blob: ffc2fd03a8f0437937ed185c4864605398f8b120 [file] [log] [blame]
/*
* 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);
}
}