| /* |
| * 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); |
| } |
| } |