aboutsummaryrefslogtreecommitdiff
path: root/apps/SdkController/src/com/android/tools/sdkcontroller/handlers/SensorChannel.java
diff options
context:
space:
mode:
Diffstat (limited to 'apps/SdkController/src/com/android/tools/sdkcontroller/handlers/SensorChannel.java')
-rwxr-xr-xapps/SdkController/src/com/android/tools/sdkcontroller/handlers/SensorChannel.java675
1 files changed, 675 insertions, 0 deletions
diff --git a/apps/SdkController/src/com/android/tools/sdkcontroller/handlers/SensorChannel.java b/apps/SdkController/src/com/android/tools/sdkcontroller/handlers/SensorChannel.java
new file mode 100755
index 000000000..ffc2fd03a
--- /dev/null
+++ b/apps/SdkController/src/com/android/tools/sdkcontroller/handlers/SensorChannel.java
@@ -0,0 +1,675 @@
+/*
+ * 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.nio.ByteBuffer;
+import java.util.ArrayList;
+import java.util.List;
+
+import android.content.Context;
+import android.hardware.Sensor;
+import android.hardware.SensorEvent;
+import android.hardware.SensorEventListener;
+import android.hardware.SensorManager;
+import android.os.Message;
+import android.os.SystemClock;
+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;
+
+/**
+ * Implements sensors emulation.
+ */
+public class SensorChannel extends Channel {
+
+ @SuppressWarnings("hiding")
+ private static String TAG = SensorChannel.class.getSimpleName();
+ @SuppressWarnings("hiding")
+ private static boolean DEBUG = false;
+ /**
+ * The target update time per sensor. Ignored if 0 or negative.
+ * Sensor updates that arrive faster than this delay are ignored.
+ * Ideally the emulator can be updated at up to 50 fps, however
+ * for average power devices something like 20 fps is more
+ * reasonable.
+ * 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}.
+ */
+ public static final int SENSOR_STATE_CHANGED = 1;
+ /**
+ * Sensor display value has changed. Parameter {@code obj} is the
+ * {@link MonitoredSensor}.
+ */
+ public static final int SENSOR_DISPLAY_MODIFIED = 2;
+
+ /**
+ * 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);
+
+ // 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 #SensorChannel(ControllerService)}.
+ *
+ * @return A non-null possibly-empty list of sensors.
+ */
+ public List<MonitoredSensor> getSensors() {
+ return mSensors;
+ }
+
+ /**
+ * Set the target update delay throttling per-sensor, in milliseconds.
+ * <p/>
+ * For example setting it to 1000/50 means that updates for a <em>given</em> sensor
+ * faster than 50 fps is discarded.
+ *
+ * @param updateTargetMs 0 to disable throttling, otherwise a > 0 millisecond minimum
+ * between sensor updates.
+ */
+ public void setUpdateTargetMs(long updateTargetMs) {
+ mUpdateTargetMs = updateTargetMs;
+ }
+
+ /**
+ * Returns the actual average time in milliseconds between same-sensor updates.
+ *
+ * @return The actual average time in milliseconds between same-sensor updates or 0.
+ */
+ public long getActualUpdateMs() {
+ return mGlobalAvgUpdateMs;
+ }
+
+ /*
+ * Channel abstract implementation.
+ */
+
+ /**
+ * This method is invoked when this channel is fully connected with its
+ * counterpart in the emulator.
+ */
+ @Override
+ public void onEmulatorConnected() {
+ // Emulation is now possible. Note though that it will start only after
+ // emulator tells us so with SENSORS_START command.
+ enable();
+ }
+
+ /**
+ * This method is invoked when this channel loses connection with its
+ * counterpart in the emulator.
+ */
+ @Override
+ public void onEmulatorDisconnected() {
+ // Stop sensor event callbacks.
+ stopSensors();
+ }
+
+ /**
+ * A query has been received from the emulator.
+ *
+ * @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.
+ */
+ @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;
+ }
+ }
+
+ /**
+ * 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.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' message.
+ *
+ * @param name Emulator-friendly name of a sensor to enable, or "all" to
+ * enable all sensors.
+ */
+ private void onEnableSensor(String name) {
+ if (name.contentEquals("all")) {
+ // Enable all sensors.
+ for (MonitoredSensor sensor : mSensors) {
+ sensor.enableSensor();
+ }
+ } else {
+ // Lookup sensor by emulator-friendly name.
+ final MonitoredSensor sensor = getSensorByEFN(name);
+ if (sensor != null) {
+ sensor.enableSensor();
+ }
+ }
+ }
+
+ /**
+ * Handles 'disable' message.
+ *
+ * @param name Emulator-friendly name of a sensor to disable, or "all" to
+ * disable all sensors.
+ */
+ private void onDisableSensor(String name) {
+ if (name.contentEquals("all")) {
+ // Disable all sensors.
+ for (MonitoredSensor sensor : mSensors) {
+ sensor.disableSensor();
+ }
+ } else {
+ // Lookup sensor by emulator-friendly name.
+ MonitoredSensor sensor = getSensorByEFN(name);
+ if (sensor != null) {
+ sensor.disableSensor();
+ }
+ }
+ }
+
+ /**
+ * Start listening to all monitored sensors.
+ */
+ private void startSensors() {
+ for (MonitoredSensor sensor : mSensors) {
+ sensor.startListening();
+ }
+ }
+
+ /**
+ * Stop listening to all monitored sensors.
+ */
+ private void stopSensors() {
+ for (MonitoredSensor sensor : mSensors) {
+ sensor.stopListening();
+ }
+ }
+
+ /***************************************************************************
+ * Internals
+ **************************************************************************/
+
+ /**
+ * 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 (MonitoredSensor sensor : mSensors) {
+ if (sensor.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 (MonitoredSensor sensor : mSensors) {
+ if (sensor.mEmulatorFriendlyName.contentEquals(name)) {
+ return sensor;
+ }
+ }
+ return null;
+ }
+
+ /**
+ * 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.
+ */
+ public class MonitoredSensor {
+ /** Sensor to monitor. */
+ private final Sensor mSensor;
+ /** The sensor name to display in the UI. */
+ private String mUiName = "";
+ /** Text view displaying the value of the sensor. */
+ private String mValue = null;
+ /** Emulator-friendly name for the sensor. */
+ private String mEmulatorFriendlyName;
+ /** Formats string to show in the TextView. */
+ private String mTextFmt;
+ /** Sensor values. */
+ private float[] mValues = new float[3];
+ /**
+ * 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 mEnabledByEmulator = false;
+ /** User-controlled enabled state. */
+ private boolean mEnabledByUser = true;
+ /** Sensor event listener for this sensor. */
+ private final OurSensorEventListener mListener = new OurSensorEventListener();
+
+ /**
+ * Constructs MonitoredSensor instance, and register the listeners.
+ *
+ * @param sensor Sensor to monitor.
+ */
+ MonitoredSensor(Sensor sensor) {
+ mSensor = sensor;
+ mEnabledByUser = true;
+
+ // 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.
+ switch (sensor.getType()) {
+ case Sensor.TYPE_ACCELEROMETER:
+ mUiName = "Accelerometer";
+ mTextFmt = "%+.2f %+.2f %+.2f";
+ mEmulatorFriendlyName = "acceleration";
+ break;
+ case 9: // Sensor.TYPE_GRAVITY is missing in API 7
+ mUiName = "Gravity";
+ mTextFmt = "%+.2f %+.2f %+.2f";
+ mEmulatorFriendlyName = "gravity";
+ break;
+ case Sensor.TYPE_GYROSCOPE:
+ mUiName = "Gyroscope";
+ mTextFmt = "%+.2f %+.2f %+.2f";
+ mEmulatorFriendlyName = "gyroscope";
+ break;
+ case Sensor.TYPE_LIGHT:
+ mUiName = "Light";
+ mTextFmt = "%.0f";
+ mEmulatorFriendlyName = "light";
+ break;
+ case 10: // Sensor.TYPE_LINEAR_ACCELERATION is missing in API 7
+ mUiName = "Linear acceleration";
+ mTextFmt = "%+.2f %+.2f %+.2f";
+ mEmulatorFriendlyName = "linear-acceleration";
+ break;
+ case Sensor.TYPE_MAGNETIC_FIELD:
+ mUiName = "Magnetic field";
+ mTextFmt = "%+.2f %+.2f %+.2f";
+ mEmulatorFriendlyName = "magnetic-field";
+ break;
+ case Sensor.TYPE_ORIENTATION:
+ mUiName = "Orientation";
+ mTextFmt = "%+03.0f %+03.0f %+03.0f";
+ mEmulatorFriendlyName = "orientation";
+ break;
+ case Sensor.TYPE_PRESSURE:
+ mUiName = "Pressure";
+ mTextFmt = "%.0f";
+ mEmulatorFriendlyName = "pressure";
+ break;
+ case Sensor.TYPE_PROXIMITY:
+ mUiName = "Proximity";
+ mTextFmt = "%.0f";
+ mEmulatorFriendlyName = "proximity";
+ break;
+ case 11: // Sensor.TYPE_ROTATION_VECTOR is missing in API 7
+ mUiName = "Rotation";
+ mTextFmt = "%+.2f %+.2f %+.2f";
+ mEmulatorFriendlyName = "rotation";
+ break;
+ case Sensor.TYPE_TEMPERATURE:
+ mUiName = "Temperature";
+ mTextFmt = "%.0f";
+ mEmulatorFriendlyName = "temperature";
+ break;
+ default:
+ mUiName = "<Unknown>";
+ mTextFmt = "N/A";
+ mEmulatorFriendlyName = "unknown";
+ 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() {
+ if (mValue == null) {
+ float[] values = mValues;
+ mValue = String.format(mTextFmt, values[0], values[1], values[2]);
+ }
+ 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;
+ }
+
+ /**
+ * 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.
+ */
+ public void onCheckedChanged(boolean isChecked) {
+ mEnabledByUser = isChecked;
+ if (isChecked) {
+ startListening();
+ } else {
+ stopListening();
+ }
+ }
+
+ /**
+ * 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 (mEnabledByEmulator && mEnabledByUser) {
+ if (DEBUG) Log.d(TAG, "+++ Sensor " + getEmulatorFriendlyName() + " is started.");
+ mSenMan.registerListener(mListener, mSensor, SensorManager.SENSOR_DELAY_FASTEST);
+ }
+ }
+
+ /**
+ * Stops monitoring the sensor.
+ * NOTE: This method is called from outside of the UI thread.
+ */
+ private void stopListening() {
+ if (DEBUG) Log.d(TAG, "--- Sensor " + getEmulatorFriendlyName() + " is stopped.");
+ mSenMan.unregisterListener(mListener);
+ }
+
+ /**
+ * Enables sensor events.
+ * NOTE: This method is called from outside of the UI thread.
+ */
+ private void enableSensor() {
+ if (DEBUG) Log.d(TAG, ">>> Sensor " + getEmulatorFriendlyName() + " is enabled.");
+ mEnabledByEmulator = true;
+ mValue = null;
+
+ Message msg = Message.obtain();
+ msg.what = SENSOR_STATE_CHANGED;
+ msg.obj = MonitoredSensor.this;
+ notifyUiHandlers(msg);
+ }
+
+ /**
+ * Disables sensor events.
+ * NOTE: This method is called from outside of the UI thread.
+ */
+ private void disableSensor() {
+ if (DEBUG) Log.w(TAG, "<<< Sensor " + getEmulatorFriendlyName() + " is disabled.");
+ mEnabledByEmulator = false;
+ mValue = "Disabled by emulator";
+
+ Message msg = Message.obtain();
+ msg.what = SENSOR_STATE_CHANGED;
+ msg.obj = MonitoredSensor.this;
+ notifyUiHandlers(msg);
+ }
+
+ private class OurSensorEventListener implements SensorEventListener {
+ /** Last update's time-stamp in local thread millisecond time. */
+ private long mLastUpdateTS = 0;
+ /** Last display update time-stamp. */
+ private long mLastDisplayTS = 0;
+ /** Preallocated buffer for change notification message. */
+ private final ByteBuffer mChangeMsg = ByteBuffer.allocate(64);
+
+ /**
+ * Handles "sensor changed" event.
+ * This is an implementation of the SensorEventListener interface.
+ */
+ @Override
+ public void onSensorChanged(SensorEvent event) {
+ long now = SystemClock.elapsedRealtime();
+
+ long deltaMs = 0;
+ if (mLastUpdateTS != 0) {
+ deltaMs = now - mLastUpdateTS;
+ if (mUpdateTargetMs > 0 && deltaMs < mUpdateTargetMs) {
+ // New sample is arriving too fast. Discard it.
+ return;
+ }
+ }
+
+ // Format and post message for the emulator.
+ float[] values = event.values;
+ final int len = values.length;
+
+ 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]);
+ }
+ }
+ postMessage(ProtocolConstants.SENSORS_SENSOR_EVENT, mChangeMsg);
+
+ // Computes average update time for this sensor and average globally.
+ if (mLastUpdateTS != 0) {
+ if (mGlobalAvgUpdateMs != 0) {
+ mGlobalAvgUpdateMs = (mGlobalAvgUpdateMs + deltaMs) / 2;
+ } else {
+ mGlobalAvgUpdateMs = deltaMs;
+ }
+ }
+ mLastUpdateTS = now;
+
+ // Update the UI for the sensor, with a static throttling of 10 fps max.
+ if (hasUiHandler()) {
+ if (mLastDisplayTS != 0) {
+ long uiDeltaMs = now - mLastDisplayTS;
+ if (uiDeltaMs < 1000 / 4 /* 4fps in ms */) {
+ // Skip this UI update
+ return;
+ }
+ }
+ mLastDisplayTS = now;
+
+ mValues[0] = values[0];
+ if (len > 1) {
+ mValues[1] = values[1];
+ if (len > 2) {
+ mValues[2] = values[2];
+ }
+ }
+ mValue = null;
+
+ Message msg = Message.obtain();
+ msg.what = SENSOR_DISPLAY_MODIFIED;
+ msg.obj = MonitoredSensor.this;
+ notifyUiHandlers(msg);
+ }
+
+ if (DEBUG) {
+ 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,
+ mSensor.getName()));
+ }
+ }
+
+ /**
+ * Handles "sensor accuracy changed" event.
+ * This is an implementation of the SensorEventListener interface.
+ */
+ @Override
+ public void onAccuracyChanged(Sensor sensor, int accuracy) {
+ }
+ }
+ } // MonitoredSensor
+
+ /***************************************************************************
+ * Logging wrappers
+ **************************************************************************/
+
+ private void Loge(String log) {
+ mService.addError(log);
+ Log.e(TAG, log);
+ }
+}