aboutsummaryrefslogtreecommitdiffstats
path: root/apps/SdkController
diff options
context:
space:
mode:
authorXavier Ducrohet <xav@android.com>2011-11-28 16:45:35 -0800
committerXavier Ducrohet <xav@android.com>2011-11-28 18:11:08 -0800
commit63079ed12d99eaa5c845c1167ef8e270af0e93db (patch)
tree2715901ed4a2ae68f420697ed8f8c665916bca7b /apps/SdkController
parent3162c11700e77c045aadaac8983716fcc779f290 (diff)
downloadsdk-63079ed12d99eaa5c845c1167ef8e270af0e93db.zip
sdk-63079ed12d99eaa5c845c1167ef8e270af0e93db.tar.gz
sdk-63079ed12d99eaa5c845c1167ef8e270af0e93db.tar.bz2
Move sdkController app to new folder.
(cherry picked from commit 892b2297a310ebdac4fe39281664ba10963b101e) Change-Id: Icca1826ce777432f635b2c2855f5638cd9bac5cf
Diffstat (limited to 'apps/SdkController')
-rw-r--r--apps/SdkController/.gitignore5
-rwxr-xr-xapps/SdkController/SdkControllerLib/AndroidManifest.xml27
-rwxr-xr-xapps/SdkController/SdkControllerLib/project.properties12
-rwxr-xr-xapps/SdkController/SdkControllerLib/res/values/strings.xml4
-rwxr-xr-xapps/SdkController/SdkControllerLib/src/com/android/tools/sdkcontroller/lib/Emulator.java848
-rw-r--r--apps/SdkController/SdkControllerLib/src/com/android/tools/sdkcontroller/lib/OnEmulatorListener.java50
-rwxr-xr-xapps/SdkController/SdkControllerSensor/AndroidManifest.xml42
-rwxr-xr-xapps/SdkController/SdkControllerSensor/project.properties12
-rwxr-xr-xapps/SdkController/SdkControllerSensor/res/drawable-hdpi/ic_launcher.pngbin0 -> 4147 bytes
-rwxr-xr-xapps/SdkController/SdkControllerSensor/res/drawable-ldpi/ic_launcher.pngbin0 -> 1723 bytes
-rwxr-xr-xapps/SdkController/SdkControllerSensor/res/drawable-mdpi/ic_launcher.pngbin0 -> 2574 bytes
-rw-r--r--apps/SdkController/SdkControllerSensor/res/layout/main.xml25
-rw-r--r--apps/SdkController/SdkControllerSensor/res/layout/one_row.xml22
-rwxr-xr-xapps/SdkController/SdkControllerSensor/res/values/strings.xml5
-rwxr-xr-xapps/SdkController/SdkControllerSensor/src/com/android/tools/sdkcontroller/sdkcontrollersensor/SdkControllerSensorActivity.java627
15 files changed, 1679 insertions, 0 deletions
diff --git a/apps/SdkController/.gitignore b/apps/SdkController/.gitignore
new file mode 100644
index 0000000..0bbcb2f
--- /dev/null
+++ b/apps/SdkController/.gitignore
@@ -0,0 +1,5 @@
+.classpath
+.project
+SdkControllerLib/bin/
+SdkControllerSensor/bin/
+SdkControllerSensor/gen/
diff --git a/apps/SdkController/SdkControllerLib/AndroidManifest.xml b/apps/SdkController/SdkControllerLib/AndroidManifest.xml
new file mode 100755
index 0000000..d93f3b5
--- /dev/null
+++ b/apps/SdkController/SdkControllerLib/AndroidManifest.xml
@@ -0,0 +1,27 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/*
+ * Copyright 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+-->
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.android.tools.sdkcontroller.lib"
+ android:versionCode="1"
+ android:versionName="1.0" >
+
+ <uses-sdk android:minSdkVersion="7" />
+ <uses-permission android:name="android.permission.INTERNET" />
+
+</manifest>
diff --git a/apps/SdkController/SdkControllerLib/project.properties b/apps/SdkController/SdkControllerLib/project.properties
new file mode 100755
index 0000000..337e8f3
--- /dev/null
+++ b/apps/SdkController/SdkControllerLib/project.properties
@@ -0,0 +1,12 @@
+# This file is automatically generated by Android Tools.
+# Do not modify this file -- YOUR CHANGES WILL BE ERASED!
+#
+# This file must be checked in Version Control Systems.
+#
+# To customize properties used by the Ant build system use,
+# "ant.properties", and override values to adapt the script to your
+# project structure.
+
+# Project target.
+target=android-7
+android.library=true
diff --git a/apps/SdkController/SdkControllerLib/res/values/strings.xml b/apps/SdkController/SdkControllerLib/res/values/strings.xml
new file mode 100755
index 0000000..35c14ff
--- /dev/null
+++ b/apps/SdkController/SdkControllerLib/res/values/strings.xml
@@ -0,0 +1,4 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources>
+
+</resources> \ No newline at end of file
diff --git a/apps/SdkController/SdkControllerLib/src/com/android/tools/sdkcontroller/lib/Emulator.java b/apps/SdkController/SdkControllerLib/src/com/android/tools/sdkcontroller/lib/Emulator.java
new file mode 100755
index 0000000..86883cc
--- /dev/null
+++ b/apps/SdkController/SdkControllerLib/src/com/android/tools/sdkcontroller/lib/Emulator.java
@@ -0,0 +1,848 @@
+/*
+ * 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.*;
+import java.nio.ByteBuffer;
+import java.nio.channels.*;
+import java.nio.channels.spi.*;
+import java.net.*;
+import java.util.*;
+import android.util.Log;
+
+/**
+ * Encapsulates a connection with the emulator. The connection is established
+ * over a TCP port forwarding enabled with 'adb forward' command.
+ *
+ * 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.
+ *
+ * Emulator is considered to be "connected" when both channels are connected.
+ * Emulator is considered to be "disconnected" when connection with any of the
+ * channels is lost.
+ *
+ * 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.
+ *
+ * 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 Emulator class implements two types of connections. One is using
+ * synchronous sockets, and another is using asynchronous sockets. The type of
+ * connection is selected when Emulator instance is created (see comments to
+ * Emulator's constructor).
+ *
+ * According to the exchange protocol with the emulator, queries, responses to
+ * the queries, and notification messages are all zero-terminated strings.
+ */
+public class Emulator {
+ /** Defines connection types supported by the Emulator 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 = "Emulator";
+ /** Emulator events listener. */
+ private OnEmulatorListener 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;
+
+ /***************************************************************************
+ * 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";
+
+ /***********************************************************************
+ * 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 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(String query_str) throws IOException {
+ String query, query_param;
+
+ // 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.
+ String response = onQuery(query, query_param);
+ if (response.length() == 0) {
+ Logw("No response to query '" + query + "'. Replying with 'ko'");
+ response = "ko:Protocol error.\0";
+ } else 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();
+ }
+ }).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(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(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
+
+ /***************************************************************************
+ * Emulator public API
+ **************************************************************************/
+
+ /**
+ * Constructs Emulator instance.
+ *
+ * @param port TCP port where emulator connects.
+ * @param ctype Defines connection type to use (sync / async). See comments
+ * to Emulator class for more info.
+ * @throws IOException
+ */
+ public Emulator(int port, EmulatorConnectionType ctype) throws IOException {
+ constructEmulator(port, ctype);
+ }
+
+ /**
+ * Constructs Emulator instance.
+ *
+ * @param port TCP port where emulator connects.
+ * @param ctype Defines connection type to use (sync / async). See comments
+ * to Emulator class for more info.
+ * @param listener Emulator event listener.
+ * @throws IOException
+ */
+ public Emulator(int port, EmulatorConnectionType ctype, OnEmulatorListener listener)
+ throws IOException {
+ mListener = listener;
+ constructEmulator(port, ctype);
+ }
+
+ /**
+ * Constructs Emulator instance.
+ *
+ * @param port TCP port where emulator connects.
+ * @param ctype Defines connection type to use (sync / async). See comments
+ * to Emulator class for more info.
+ * @throws IOException
+ */
+ private void constructEmulator(int port, EmulatorConnectionType ctype) throws IOException {
+ 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();
+ InetSocketAddress address = new InetSocketAddress(local, port);
+ mServerSocket.socket().bind(address);
+
+ // Register 'accept' I/O on the server socket.
+ mServerSocket.register(mSelector, SelectionKey.OP_ACCEPT);
+
+ // Start I/O looper and dispatcher.
+ new Thread(new Runnable() {
+ @Override
+ public void run() {
+ runIOLooper();
+ }
+ }).start();
+ }
+
+ /**
+ * Sends a notification message to the emulator via 'event' channel.
+ *
+ * @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 Emulator");
+ }
+ }
+
+ /**
+ * 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 setOnEmulatorListener(OnEmulatorListener 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();
+ }
+ }
+ }
+
+ /***************************************************************************
+ * Emulator 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() {
+ OnEmulatorListener 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() {
+ OnEmulatorListener 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) {
+ OnEmulatorListener listener;
+ synchronized (this) {
+ listener = mListener;
+ }
+ if (listener != null) {
+ return listener.onEmulatorQuery(query, param);
+ } else {
+ return "ko:Service is detached.\0";
+ }
+ }
+
+ /***************************************************************************
+ * Emulator implementation
+ **************************************************************************/
+
+ /**
+ * Loops on the selector, handling and dispatching I/O events.
+ */
+ private void runIOLooper() {
+ try {
+ Logv("Waiting on Emulator to connect...");
+ while (mSelector.select() >= 0) {
+ 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.
+ 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("Emulator 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!
+ socket.getOutputStream().write("ko:Duplicate,\0".getBytes());
+ Loge("Duplicate query channel.");
+ 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 {
+ socket.getOutputStream().write("ko:Duplicate,\0".getBytes());
+ Loge("Duplicate event channel.");
+ channel.close();
+ return;
+ }
+ } else {
+ Loge("Unknown channel is connecting: " + socket_type);
+ 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("... Emulator 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.");
+ synchronized (this) {
+ // Close all channels.
+ if (mSelector != null) {
+ try {
+ if (mEventChannel != null) {
+ mEventChannel.closeChannel();
+ }
+ if (mQueryChannel != null) {
+ mQueryChannel.closeChannel();
+ }
+ mServerSocket.close();
+ 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();
+ }
+
+ /***************************************************************************
+ * 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/SdkControllerLib/src/com/android/tools/sdkcontroller/lib/OnEmulatorListener.java b/apps/SdkController/SdkControllerLib/src/com/android/tools/sdkcontroller/lib/OnEmulatorListener.java
new file mode 100644
index 0000000..6a28156
--- /dev/null
+++ b/apps/SdkController/SdkControllerLib/src/com/android/tools/sdkcontroller/lib/OnEmulatorListener.java
@@ -0,0 +1,50 @@
+/*
+ * 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 Emulator instance via
+ * setOnEmulatorListener method of the Emulator class.
+ */
+public interface OnEmulatorListener {
+ /**
+ * 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. String must be formatted as such:
+ * "ok|ko[:reply data]"
+ */
+ public String onEmulatorQuery(String query, String param);
+}
diff --git a/apps/SdkController/SdkControllerSensor/AndroidManifest.xml b/apps/SdkController/SdkControllerSensor/AndroidManifest.xml
new file mode 100755
index 0000000..0255cc9
--- /dev/null
+++ b/apps/SdkController/SdkControllerSensor/AndroidManifest.xml
@@ -0,0 +1,42 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/*
+ * Copyright 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+-->
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.android.tools.sdkcontroller.sdkcontrollersensor"
+ android:versionCode="1"
+ android:versionName="1.0" >
+
+ <uses-sdk android:minSdkVersion="7" />
+ <uses-permission android:name="android.permission.INTERNET" />
+
+ <application
+ android:icon="@drawable/ic_launcher"
+ android:label="@string/app_name" >
+ <activity
+ android:label="@string/app_name"
+ android:configChanges="orientation|keyboardHidden|mcc|mnc|locale|touchscreen|keyboard|navigation|screenLayout|fontScale"
+ android:name=".SdkControllerSensorActivity" >
+ <intent-filter >
+ <action android:name="android.intent.action.MAIN" />
+
+ <category android:name="android.intent.category.LAUNCHER" />
+ </intent-filter>
+ </activity>
+ </application>
+
+</manifest>
diff --git a/apps/SdkController/SdkControllerSensor/project.properties b/apps/SdkController/SdkControllerSensor/project.properties
new file mode 100755
index 0000000..d3d8dc4
--- /dev/null
+++ b/apps/SdkController/SdkControllerSensor/project.properties
@@ -0,0 +1,12 @@
+# This file is automatically generated by Android Tools.
+# Do not modify this file -- YOUR CHANGES WILL BE ERASED!
+#
+# This file must be checked in Version Control Systems.
+#
+# To customize properties used by the Ant build system use,
+# "ant.properties", and override values to adapt the script to your
+# project structure.
+
+# Project target.
+target=android-7
+android.library.reference.1=../SdkControllerLib
diff --git a/apps/SdkController/SdkControllerSensor/res/drawable-hdpi/ic_launcher.png b/apps/SdkController/SdkControllerSensor/res/drawable-hdpi/ic_launcher.png
new file mode 100755
index 0000000..8074c4c
--- /dev/null
+++ b/apps/SdkController/SdkControllerSensor/res/drawable-hdpi/ic_launcher.png
Binary files differ
diff --git a/apps/SdkController/SdkControllerSensor/res/drawable-ldpi/ic_launcher.png b/apps/SdkController/SdkControllerSensor/res/drawable-ldpi/ic_launcher.png
new file mode 100755
index 0000000..1095584
--- /dev/null
+++ b/apps/SdkController/SdkControllerSensor/res/drawable-ldpi/ic_launcher.png
Binary files differ
diff --git a/apps/SdkController/SdkControllerSensor/res/drawable-mdpi/ic_launcher.png b/apps/SdkController/SdkControllerSensor/res/drawable-mdpi/ic_launcher.png
new file mode 100755
index 0000000..a07c69f
--- /dev/null
+++ b/apps/SdkController/SdkControllerSensor/res/drawable-mdpi/ic_launcher.png
Binary files differ
diff --git a/apps/SdkController/SdkControllerSensor/res/layout/main.xml b/apps/SdkController/SdkControllerSensor/res/layout/main.xml
new file mode 100644
index 0000000..d3b9426
--- /dev/null
+++ b/apps/SdkController/SdkControllerSensor/res/layout/main.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="utf-8"?>
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="fill_parent"
+ android:layout_height="fill_parent"
+ android:orientation="vertical" >
+
+ <TextView
+ android:layout_width="fill_parent"
+ android:layout_height="wrap_content" />
+
+ <ScrollView
+ android:id="@+id/scrollView1"
+ android:layout_width="fill_parent"
+ android:layout_height="fill_parent"
+ android:layout_weight="1" >
+
+ <TableLayout
+ android:id="@+id/tableLayout"
+ android:layout_width="fill_parent"
+ android:layout_height="wrap_content"
+ android:saveEnabled="false" />
+
+ </ScrollView>
+
+</LinearLayout> \ No newline at end of file
diff --git a/apps/SdkController/SdkControllerSensor/res/layout/one_row.xml b/apps/SdkController/SdkControllerSensor/res/layout/one_row.xml
new file mode 100644
index 0000000..b8dee45
--- /dev/null
+++ b/apps/SdkController/SdkControllerSensor/res/layout/one_row.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="utf-8"?>
+<TableRow xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="fill_parent"
+ android:layout_height="fill_parent" >
+
+ <CheckBox
+ android:id="@+id/row_checkbox"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:text="Some CheckBox"
+ android:saveEnabled="false"
+ android:layout_marginRight="10dp"
+ />
+
+ <TextView
+ android:id="@+id/row_textview"
+ android:layout_width="fill_parent"
+ android:layout_height="wrap_content"
+ android:layout_weight="1"
+ android:textAppearance="?android:attr/textAppearanceSmall" />
+
+</TableRow> \ No newline at end of file
diff --git a/apps/SdkController/SdkControllerSensor/res/values/strings.xml b/apps/SdkController/SdkControllerSensor/res/values/strings.xml
new file mode 100755
index 0000000..29bfa5f
--- /dev/null
+++ b/apps/SdkController/SdkControllerSensor/res/values/strings.xml
@@ -0,0 +1,5 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources>
+ <string name="app_name">SDK Controller Sensor</string>
+
+</resources>
diff --git a/apps/SdkController/SdkControllerSensor/src/com/android/tools/sdkcontroller/sdkcontrollersensor/SdkControllerSensorActivity.java b/apps/SdkController/SdkControllerSensor/src/com/android/tools/sdkcontroller/sdkcontrollersensor/SdkControllerSensorActivity.java
new file mode 100755
index 0000000..a01926d
--- /dev/null
+++ b/apps/SdkController/SdkControllerSensor/src/com/android/tools/sdkcontroller/sdkcontrollersensor/SdkControllerSensorActivity.java
@@ -0,0 +1,627 @@
+/*
+ * 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.sdkcontrollersensor;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.io.IOException;
+
+import android.app.Activity;
+import android.hardware.SensorEvent;
+import android.hardware.SensorEventListener;
+import android.hardware.SensorManager;
+import android.hardware.Sensor;
+import android.os.Bundle;
+import android.widget.CheckBox;
+import android.widget.TextView;
+import android.widget.TableLayout;
+import android.widget.TableRow;
+import android.widget.CompoundButton;
+import android.view.LayoutInflater;
+import android.util.Log;
+
+import com.android.tools.sdkcontroller.lib.Emulator;
+import com.android.tools.sdkcontroller.lib.Emulator.EmulatorConnectionType;
+import com.android.tools.sdkcontroller.lib.OnEmulatorListener;
+
+/**
+ * Encapsulates an application that monitors all sensors available on a device,
+ * and sends acquired sensor values to an Android Emulator application running
+ * on the host machine. This application is used to provide a realistic sensor
+ * emulation in Android Emulator.
+ */
+public class SdkControllerSensorActivity extends Activity implements OnEmulatorListener {
+ /** Tag for logging messages. */
+ private static final String TAG = "SdkControllerSensor";
+
+ /** TCP over USB connection to the emulator. */
+ private Emulator mEmulator;
+ /** Array containing monitored sensors. */
+ private List<MonitoredSensor> mSensors;
+ /** Controls displayed list of sensors. */
+ private TableLayout mTableLayout;
+
+ /**
+ * Encapsulates a sensor that is being monitored.
+ *
+ * To monitor sensor changes each monitored sensor registers with sensor
+ * manager as a sensor listener.
+ *
+ * To control sensor monitoring from the UI, each monitored sensor has two
+ * UI controls associated with it:
+ * - A check box (named after sensor) that can be used to enable,
+ * or disable listening to the sensor changes.
+ * - A text view where current sensor value is displayed.
+ */
+ private class MonitoredSensor implements SensorEventListener,
+ CompoundButton.OnCheckedChangeListener {
+ /** Sensor to monitor. */
+ private final Sensor mSensor;
+ /** Check box representing the sensor on the screen. */
+ private final CheckBox mChk;
+ /** Text view displaying the value of the sensor. */
+ private final TextView mVal;
+ /** Emulator-friendly name for the sensor. */
+ private String mEmulatorFriendlyName;
+ /** Formats string to show in the TextView. */
+ private String mTextFmt;
+ /** Formats string to send to the emulator. */
+ private String mMsgFmt;
+ /**
+ * Enabled state. This state is controlled by the emulator, that
+ * maintains its own list of sensors. So, if a sensor is missing, or is
+ * disabled in the emulator, it should be disabled in this application.
+ */
+ private boolean mEnabled = false;
+ /** Checked state. */
+ private boolean mChecked = true;
+
+ /**
+ * Constructs MonitoredSensor instance, and register the listeners.
+ *
+ * @param sensor Sensor to monitor.
+ */
+ MonitoredSensor(Sensor sensor) {
+ mSensor = sensor;
+ mChecked = true;
+
+ // Add a row representing this sensor on the display
+ final LayoutInflater inflater = getLayoutInflater();
+ final TableRow row = (TableRow) inflater.inflate(R.layout.one_row, mTableLayout, false);
+ mTableLayout.addView(row);
+
+ // Initialize displayed checkbox for this sensor, and register
+ // checked state listener for it.
+ mChk = (CheckBox) row.findViewById(R.id.row_checkbox);
+ mChk.setChecked(true);
+ mChk.setOnCheckedChangeListener(this);
+
+ // Initialize displayed text box for this sensor.
+ mVal = (TextView) row.findViewById(R.id.row_textview);
+ mVal.setText("");
+
+ // Set appropriate sensor name depending on the type. Unfortunately,
+ // 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.
+ switch (sensor.getType()) {
+ case Sensor.TYPE_ACCELEROMETER:
+ mChk.setText("Accelerometer");
+ // 3 floats.
+ mTextFmt = "%+.2f %+.2f %+.2f";
+ mEmulatorFriendlyName = "acceleration";
+ mMsgFmt = mEmulatorFriendlyName + ":%g:%g:%g\0";
+ break;
+ case 9: // Sensor.TYPE_GRAVITY is missing in API 7
+ // 3 floats.
+ mChk.setText("Gravity");
+ mTextFmt = "%+.2f %+.2f %+.2f";
+ mEmulatorFriendlyName = "gravity";
+ mMsgFmt = mEmulatorFriendlyName + ":%g:%g:%g\0";
+ break;
+ case Sensor.TYPE_GYROSCOPE:
+ mChk.setText("Gyroscope");
+ // 3 floats.
+ mTextFmt = "%+.2f %+.2f %+.2f";
+ mEmulatorFriendlyName = "gyroscope";
+ mMsgFmt = mEmulatorFriendlyName + ":%g:%g:%g\0";
+ break;
+ case Sensor.TYPE_LIGHT:
+ mChk.setText("Light");
+ // 1 integer.
+ mTextFmt = "%.0f";
+ mEmulatorFriendlyName = "light";
+ mMsgFmt = mEmulatorFriendlyName + ":%g\0";
+ break;
+ case 10: // Sensor.TYPE_LINEAR_ACCELERATION is missing in API 7
+ mChk.setText("Linear acceleration");
+ // 3 floats.
+ mTextFmt = "%+.2f %+.2f %+.2f";
+ mEmulatorFriendlyName = "linear-acceleration";
+ mMsgFmt = mEmulatorFriendlyName + ":%g:%g:%g\0";
+ break;
+ case Sensor.TYPE_MAGNETIC_FIELD:
+ mChk.setText("Magnetic field");
+ // 3 floats.
+ mTextFmt = "%+.2f %+.2f %+.2f";
+ mEmulatorFriendlyName = "magnetic-field";
+ mMsgFmt = mEmulatorFriendlyName + ":%g:%g:%g\0";
+ break;
+ case Sensor.TYPE_ORIENTATION:
+ mChk.setText("Orientation");
+ // 3 integers.
+ mTextFmt = "%+03.0f %+03.0f %+03.0f";
+ mEmulatorFriendlyName = "orientation";
+ mMsgFmt = mEmulatorFriendlyName + ":%g:%g:%g\0";
+ break;
+ case Sensor.TYPE_PRESSURE:
+ mChk.setText("Pressure");
+ // 1 integer.
+ mTextFmt = "%.0f";
+ mEmulatorFriendlyName = "pressure";
+ mMsgFmt = mEmulatorFriendlyName + ":%g\0";
+ break;
+ case Sensor.TYPE_PROXIMITY:
+ mChk.setText("Proximity");
+ // 1 integer.
+ mTextFmt = "%.0f";
+ mEmulatorFriendlyName = "proximity";
+ mMsgFmt = mEmulatorFriendlyName + ":%g\0";
+ break;
+ case 11: // Sensor.TYPE_ROTATION_VECTOR is missing in API 7
+ mChk.setText("Rotation");
+ // 3 floats.
+ mTextFmt = "%+.2f %+.2f %+.2f";
+ mEmulatorFriendlyName = "rotation";
+ mMsgFmt = mEmulatorFriendlyName + ":%g:%g:%g\0";
+ break;
+ case Sensor.TYPE_TEMPERATURE:
+ mChk.setText("Temperature");
+ // 1 integer.
+ mTextFmt = "%.0f";
+ mEmulatorFriendlyName = "tempterature";
+ mMsgFmt = mEmulatorFriendlyName + ":%g\0";
+ break;
+ default:
+ mChk.setText("<Unknown>");
+ mTextFmt = "N/A";
+ mEmulatorFriendlyName = "unknown";
+ mMsgFmt = mEmulatorFriendlyName + "\0";
+ Loge("Unknown sensor type " + mSensor.getType() + " for sensor "
+ + mSensor.getName());
+ break;
+ }
+ }
+
+ /**
+ * Gets sensor type.
+ *
+ * @return Sensor type as one of the Sensor.TYPE_XXX constants.
+ */
+ private int getType() {
+ return mSensor.getType();
+ }
+
+ /**
+ * Gets sensor's emulator-friendly name.
+ *
+ * @return Sensor's emulator-friendly name.
+ */
+ private String getEmulatorFriendlyName() {
+ return mEmulatorFriendlyName;
+ }
+
+ /**
+ * Starts monitoring the sensor. NOTE: This method is called from
+ * outside of the UI thread.
+ */
+ private void startListening() {
+ if (mEnabled && mChecked) {
+ Logv("+++ Sensor " + getEmulatorFriendlyName() + " is started.");
+ SensorManager sm = (SensorManager) getSystemService(SENSOR_SERVICE);
+ sm.registerListener(this, mSensor, SensorManager.SENSOR_DELAY_UI);
+ }
+ }
+
+ /**
+ * Stops monitoring the sensor. NOTE: This method is called from outside
+ * of the UI thread.
+ */
+ private void stopListening() {
+ Logv("--- Sensor " + getEmulatorFriendlyName() + " is stopped.");
+ SensorManager sm = (SensorManager) getSystemService(SENSOR_SERVICE);
+ sm.unregisterListener(this);
+ }
+
+ /**
+ * Enables sensor events. NOTE: This method is called from outside of
+ * the UI thread.
+ */
+ private void enableSensor() {
+ Logv(">>> Sensor " + getEmulatorFriendlyName() + " is enabled.");
+ mEnabled = true;
+ mChk.post(new Runnable() {
+ @Override
+ public void run() {
+ mChk.setEnabled(true);
+ mVal.setText("");
+ mVal.setEnabled(true);
+ }
+ });
+ }
+
+ /**
+ * Disables sensor events. NOTE: This method is called from outside of
+ * the UI thread.
+ */
+ private void disableSensor() {
+ Logv("<<< Sensor " + getEmulatorFriendlyName() + " is disabled.");
+ mEnabled = false;
+ mChk.post(new Runnable() {
+ @Override
+ public void run() {
+ mChk.setEnabled(false);
+ mVal.setText("Disabled");
+ mVal.setEnabled(false);
+ }
+ });
+ }
+
+ /**
+ * Handles checked state change for the associated CheckBox. If check
+ * box is checked we will register sensor change listener. If it is
+ * unchecked, we will unregister sensor change listener.
+ */
+ @Override
+ public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
+ mChecked = isChecked;
+ if (isChecked) {
+ startListening();
+ } else {
+ stopListening();
+ }
+ }
+
+ /**
+ * Handles "sensor changed" event. This is an implementation of the
+ * SensorEventListener interface.
+ */
+ @Override
+ public void onSensorChanged(SensorEvent event) {
+ // Display current sensor value, and format message that will be
+ // sent to the emulator.
+ final int nArgs = event.values.length;
+ String msg;
+ String val;
+ if (nArgs == 3) {
+ val = String.format(mTextFmt, event.values[0], event.values[1], event.values[2]);
+ msg = String.format(mMsgFmt, event.values[0], event.values[1], event.values[2]);
+ } else if (nArgs == 2) {
+ val = String.format(mTextFmt, event.values[0], event.values[1]);
+ msg = String.format(mMsgFmt, event.values[0], event.values[1]);
+ } else if (nArgs == 1) {
+ val = String.format(mTextFmt, event.values[0]);
+ msg = String.format(mMsgFmt, event.values[0]);
+ } else {
+ Loge("Unexpected number of values " + event.values.length
+ + " in onSensorChanged for sensor " + mSensor.getName());
+ return;
+ }
+ mVal.setText(val);
+ sendSensorEvent(msg);
+ }
+
+ /**
+ * Handles "sensor accuracy changed" event. This is an implementation of
+ * the SensorEventListener interface.
+ */
+ @Override
+ public void onAccuracyChanged(Sensor sensor, int accuracy) {
+ }
+ } // MonitoredSensor
+
+ /***************************************************************************
+ * SdkControllerSensor implementation
+ **************************************************************************/
+
+ /** Called when the activity is first created. */
+ @Override
+ public void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ setContentView(R.layout.main);
+
+ mTableLayout = (TableLayout) findViewById(R.id.tableLayout);
+
+ // Iterate through the available sensors, adding them to the array.
+ mSensors = new ArrayList<MonitoredSensor>();
+ SensorManager sm = (SensorManager) getSystemService(SENSOR_SERVICE);
+ 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);
+ Logv(String.format("Monitoring sensor #%02d: Name = '%s', Type = 0x%x",
+ cur_index, def_sens.getName(), def_sens.getType()));
+ }
+ }
+
+ // Instantiate emulator connector.
+ try {
+ // Sensor emulator starts very early during emulator startup. So, as
+ // discussed in comments to Emulator class, we must use synchronous
+ // type of connection with the emulator.
+ mEmulator = new Emulator(Emulator.SENSORS_PORT, EmulatorConnectionType.SYNC_CONNECTION,
+ this);
+ } catch (IOException e) {
+ Loge("Exception while creating server socket: " + e.getMessage());
+ finish();
+ }
+ }
+
+ /**
+ * Sends sensor's event to the emulator.
+ *
+ * @param msg Sensor's event message.
+ */
+ public void sendSensorEvent(String msg) {
+ mEmulator.sendNotification(msg);
+ }
+
+ /***************************************************************************
+ * OnEmulatorListener implementation
+ **************************************************************************/
+
+ /**
+ * 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.
+ */
+ @Override
+ public void onEmulatorConnected() {
+ }
+
+ /**
+ * Called when emulator is disconnected.
+ */
+ @Override
+ public void onEmulatorDisconnected() {
+ // Stop listening to sensors, and let it cool for a sec...
+ stopSensors();
+ try {
+ Thread.sleep(500);
+ } catch (Exception e) {
+ }
+
+ // Instantiate emulator connector for the next client.
+ try {
+ mEmulator = new Emulator(Emulator.SENSORS_PORT, EmulatorConnectionType.SYNC_CONNECTION,
+ this);
+ } catch (IOException e) {
+ Loge("Exception while creating server socket: " + e.getMessage());
+ finish();
+ }
+ }
+
+ /**
+ * 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]"
+ */
+ @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 {
+ Loge("Unknown query " + query + "(" + param + ")");
+ return "ko:Query is unknown\0";
+ }
+ }
+
+ /***************************************************************************
+ * Query handlers
+ **************************************************************************/
+
+ /**
+ * Handles 'list' query.
+ *
+ * @return List of emulator-friendly names for sensors that are available on
+ * the device.
+ */
+ private String onQueryList() {
+ // List monitored sensors.
+ String list = "ok:";
+ for (int n = 0; n < mSensors.size(); n++) {
+ list += mSensors.get(n).getEmulatorFriendlyName();
+ list += "\n";
+ }
+ 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.
+ *
+ * @return Empty string. This is a "command" query that doesn't assume any
+ * response.
+ */
+ private String onQueryStop() {
+ stopSensors();
+ return "ok\0";
+ }
+
+ /**
+ * Handles 'enable' query.
+ *
+ * @param param Sensor selector: - all Enables all available sensors, or -
+ * <name> Emulator-friendly name of a sensor to enable.
+ * @return "ok" / "ko": success / failure.
+ */
+ private String onQueryEnable(String param) {
+ if (param.contentEquals("all")) {
+ // Enable all sensors.
+ for (int n = 0; n < mSensors.size(); n++) {
+ mSensors.get(n).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";
+ }
+ }
+
+ /**
+ * Handles 'disable' query.
+ *
+ * @param param Sensor selector: - all Disables all available sensors, or -
+ * <name> Emulator-friendly name of a sensor to disable.
+ * @return "ok" / "ko": success / failure.
+ */
+ private String onQueryDisable(String param) {
+ if (param.contentEquals("all")) {
+ // Disable all sensors.
+ for (int n = 0; n < mSensors.size(); n++) {
+ mSensors.get(n).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";
+ }
+ }
+
+ /***************************************************************************
+ * Internals
+ **************************************************************************/
+
+ /**
+ * Start listening to all monitored sensors.
+ */
+ private void startSensors() {
+ for (int n = 0; n < mSensors.size(); n++) {
+ mSensors.get(n).startListening();
+ }
+ }
+
+ /**
+ * Stop listening to all monitored sensors.
+ */
+ private void stopSensors() {
+ for (int n = 0; n < mSensors.size(); n++) {
+ mSensors.get(n).stopListening();
+ }
+ }
+
+ /**
+ * Checks if a sensor for the given type is already monitored.
+ *
+ * @param type Sensor type (one of the Sensor.TYPE_XXX constants)
+ * @return true if a sensor for the given type is already monitored, or
+ * false if the sensor is not monitored.
+ */
+ private boolean isSensorTypeAlreadyMonitored(int type) {
+ for (int n = 0; n < mSensors.size(); n++) {
+ if (mSensors.get(n).getType() == type) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ /**
+ * Looks up a monitored sensor by its emulator-friendly name.
+ *
+ * @param name Emulator-friendly name to look up the monitored sensor for.
+ * @return Monitored sensor for the fiven name, or null if sensor was not
+ * found.
+ */
+ private MonitoredSensor getSensorByEFN(String name) {
+ for (int n = 0; n < mSensors.size(); n++) {
+ MonitoredSensor sensor = mSensors.get(n);
+ if (sensor.mEmulatorFriendlyName.contentEquals(name)) {
+ return sensor;
+ }
+ }
+ return null;
+ }
+
+ /***************************************************************************
+ * Logging wrappers
+ **************************************************************************/
+
+ private void Loge(String log) {
+ Log.e(TAG, log);
+ }
+
+ private void Logv(String log) {
+ Log.v(TAG, log);
+ }
+
+} // SdkControllerSensor