2 * Copyright (C) 2012 The Android Open Source Project
4 * Licensed under the Apache License, Version 2.0 (the "License"); you may not
5 * use this file except in compliance with the License. You may obtain a copy of
8 * http://www.apache.org/licenses/LICENSE-2.0
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
12 * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
13 * License for the specific language governing permissions and limitations under
17 package com
.android
.tools
.sdkcontroller
.handlers
;
19 import java
.nio
.ByteBuffer
;
20 import java
.util
.ArrayList
;
21 import java
.util
.List
;
23 import android
.content
.Context
;
24 import android
.hardware
.Sensor
;
25 import android
.hardware
.SensorEvent
;
26 import android
.hardware
.SensorEventListener
;
27 import android
.hardware
.SensorManager
;
28 import android
.os
.Message
;
29 import android
.os
.SystemClock
;
30 import android
.util
.Log
;
32 import com
.android
.tools
.sdkcontroller
.lib
.Channel
;
33 import com
.android
.tools
.sdkcontroller
.lib
.ProtocolConstants
;
34 import com
.android
.tools
.sdkcontroller
.service
.ControllerService
;
37 * Implements sensors emulation.
39 public class SensorChannel
extends Channel
{
41 @SuppressWarnings("hiding")
42 private static String TAG
= SensorChannel
.class.getSimpleName();
43 @SuppressWarnings("hiding")
44 private static boolean DEBUG
= false;
46 * The target update time per sensor. Ignored if 0 or negative.
47 * Sensor updates that arrive faster than this delay are ignored.
48 * Ideally the emulator can be updated at up to 50 fps, however
49 * for average power devices something like 20 fps is more
51 * Default value should match res/values/strings.xml > sensors_default_sample_rate.
53 private long mUpdateTargetMs
= 1000/20; // 20 fps in milliseconds
54 /** Accumulates average update frequency. */
55 private long mGlobalAvgUpdateMs
= 0;
57 /** Array containing monitored sensors. */
58 private final List
<MonitoredSensor
> mSensors
= new ArrayList
<MonitoredSensor
>();
59 /** Sensor manager. */
60 private SensorManager mSenMan
;
63 * Messages exchanged with the UI.
67 * Sensor "enabled by emulator" state has changed. Parameter {@code obj} is
68 * the {@link MonitoredSensor}.
70 public static final int SENSOR_STATE_CHANGED
= 1;
72 * Sensor display value has changed. Parameter {@code obj} is the
73 * {@link MonitoredSensor}.
75 public static final int SENSOR_DISPLAY_MODIFIED
= 2;
78 * Constructs SensorChannel instance.
80 * @param service Service context.
82 public SensorChannel(ControllerService service
) {
83 super(service
, Channel
.SENSOR_CHANNEL
);
84 mSenMan
= (SensorManager
) service
.getSystemService(Context
.SENSOR_SERVICE
);
85 // Iterate through the available sensors, adding them to the array.
86 List
<Sensor
> sensors
= mSenMan
.getSensorList(Sensor
.TYPE_ALL
);
88 for (int n
= 0; n
< sensors
.size(); n
++) {
89 Sensor avail_sensor
= sensors
.get(n
);
91 // There can be multiple sensors of the same type. We need only one.
92 if (!isSensorTypeAlreadyMonitored(avail_sensor
.getType())) {
93 // The first sensor we've got for the given type is not
94 // necessarily the right one. So, use the default sensor
95 // for the given type.
96 Sensor def_sens
= mSenMan
.getDefaultSensor(avail_sensor
.getType());
97 MonitoredSensor to_add
= new MonitoredSensor(def_sens
);
101 Log
.d(TAG
, String
.format(
102 "Monitoring sensor #%02d: Name = '%s', Type = 0x%x",
103 cur_index
, def_sens
.getName(), def_sens
.getType()));
109 * Returns the list of sensors found on the device.
110 * The list is computed once by {@link #SensorChannel(ControllerService)}.
112 * @return A non-null possibly-empty list of sensors.
114 public List
<MonitoredSensor
> getSensors() {
119 * Set the target update delay throttling per-sensor, in milliseconds.
121 * For example setting it to 1000/50 means that updates for a <em>given</em> sensor
122 * faster than 50 fps is discarded.
124 * @param updateTargetMs 0 to disable throttling, otherwise a > 0 millisecond minimum
125 * between sensor updates.
127 public void setUpdateTargetMs(long updateTargetMs
) {
128 mUpdateTargetMs
= updateTargetMs
;
132 * Returns the actual average time in milliseconds between same-sensor updates.
134 * @return The actual average time in milliseconds between same-sensor updates or 0.
136 public long getActualUpdateMs() {
137 return mGlobalAvgUpdateMs
;
141 * Channel abstract implementation.
145 * This method is invoked when this channel is fully connected with its
146 * counterpart in the emulator.
149 public void onEmulatorConnected() {
150 // Emulation is now possible. Note though that it will start only after
151 // emulator tells us so with SENSORS_START command.
156 * This method is invoked when this channel loses connection with its
157 * counterpart in the emulator.
160 public void onEmulatorDisconnected() {
161 // Stop sensor event callbacks.
166 * A query has been received from the emulator.
168 * @param query_id Identifies the query. This ID should be used when
169 * replying to the query.
170 * @param query_type Query type.
171 * @param query_data Query data.
174 public void onEmulatorQuery(int query_id
, int query_type
, ByteBuffer query_data
) {
175 switch (query_type
) {
176 case ProtocolConstants
.SENSORS_QUERY_LIST
:
177 // Preallocate large response buffer.
178 ByteBuffer resp
= ByteBuffer
.allocate(1024);
179 resp
.order(getEndian());
180 // Iterate through the list of monitored sensors, dumping them
181 // into the response buffer.
182 for (MonitoredSensor sensor
: mSensors
) {
183 // Entry for each sensor must contain:
184 // - an integer for its ID
185 // - a zero-terminated emulator-friendly name.
186 final byte[] name
= sensor
.getEmulatorFriendlyName().getBytes();
187 final int required_size
= 4 + name
.length
+ 1;
188 resp
= ExpandIf(resp
, required_size
);
189 resp
.putInt(sensor
.getType());
193 // Terminating entry contains single -1 integer.
194 resp
= ExpandIf(resp
, 4);
196 sendQueryResponse(query_id
, resp
);
200 Loge("Unknown query " + query_type
);
206 * A message has been received from the emulator.
208 * @param msg_type Message type.
209 * @param msg_data Packet received from the emulator.
212 public void onEmulatorMessage(int msg_type
, ByteBuffer msg_data
) {
214 case ProtocolConstants
.SENSORS_START
:
215 Log
.v(TAG
, "Starting sensors emulation.");
218 case ProtocolConstants
.SENSORS_STOP
:
219 Log
.v(TAG
, "Stopping sensors emulation.");
222 case ProtocolConstants
.SENSORS_ENABLE
:
223 String enable_name
= new String(msg_data
.array());
224 Log
.v(TAG
, "Enabling sensor: " + enable_name
);
225 onEnableSensor(enable_name
);
227 case ProtocolConstants
.SENSORS_DISABLE
:
228 String disable_name
= new String(msg_data
.array());
229 Log
.v(TAG
, "Disabling sensor: " + disable_name
);
230 onDisableSensor(disable_name
);
233 Loge("Unknown message type " + msg_type
);
239 * Handles 'enable' message.
241 * @param name Emulator-friendly name of a sensor to enable, or "all" to
242 * enable all sensors.
244 private void onEnableSensor(String name
) {
245 if (name
.contentEquals("all")) {
246 // Enable all sensors.
247 for (MonitoredSensor sensor
: mSensors
) {
248 sensor
.enableSensor();
251 // Lookup sensor by emulator-friendly name.
252 final MonitoredSensor sensor
= getSensorByEFN(name
);
253 if (sensor
!= null) {
254 sensor
.enableSensor();
260 * Handles 'disable' message.
262 * @param name Emulator-friendly name of a sensor to disable, or "all" to
263 * disable all sensors.
265 private void onDisableSensor(String name
) {
266 if (name
.contentEquals("all")) {
267 // Disable all sensors.
268 for (MonitoredSensor sensor
: mSensors
) {
269 sensor
.disableSensor();
272 // Lookup sensor by emulator-friendly name.
273 MonitoredSensor sensor
= getSensorByEFN(name
);
274 if (sensor
!= null) {
275 sensor
.disableSensor();
281 * Start listening to all monitored sensors.
283 private void startSensors() {
284 for (MonitoredSensor sensor
: mSensors
) {
285 sensor
.startListening();
290 * Stop listening to all monitored sensors.
292 private void stopSensors() {
293 for (MonitoredSensor sensor
: mSensors
) {
294 sensor
.stopListening();
298 /***************************************************************************
300 **************************************************************************/
303 * Checks if a sensor for the given type is already monitored.
305 * @param type Sensor type (one of the Sensor.TYPE_XXX constants)
306 * @return true if a sensor for the given type is already monitored, or
307 * false if the sensor is not monitored.
309 private boolean isSensorTypeAlreadyMonitored(int type
) {
310 for (MonitoredSensor sensor
: mSensors
) {
311 if (sensor
.getType() == type
) {
319 * Looks up a monitored sensor by its emulator-friendly name.
321 * @param name Emulator-friendly name to look up the monitored sensor for.
322 * @return Monitored sensor for the fiven name, or null if sensor was not
325 private MonitoredSensor
getSensorByEFN(String name
) {
326 for (MonitoredSensor sensor
: mSensors
) {
327 if (sensor
.mEmulatorFriendlyName
.contentEquals(name
)) {
335 * Encapsulates a sensor that is being monitored. To monitor sensor changes
336 * each monitored sensor registers with sensor manager as a sensor listener.
337 * To control sensor monitoring from the UI, each monitored sensor has two
338 * UI controls associated with it: - A check box (named after sensor) that
339 * can be used to enable, or disable listening to the sensor changes. - A
340 * text view where current sensor value is displayed.
342 public class MonitoredSensor
{
343 /** Sensor to monitor. */
344 private final Sensor mSensor
;
345 /** The sensor name to display in the UI. */
346 private String mUiName
= "";
347 /** Text view displaying the value of the sensor. */
348 private String mValue
= null;
349 /** Emulator-friendly name for the sensor. */
350 private String mEmulatorFriendlyName
;
351 /** Formats string to show in the TextView. */
352 private String mTextFmt
;
353 /** Sensor values. */
354 private float[] mValues
= new float[3];
356 * Enabled state. This state is controlled by the emulator, that
357 * maintains its own list of sensors. So, if a sensor is missing, or is
358 * disabled in the emulator, it should be disabled in this application.
360 private boolean mEnabledByEmulator
= false;
361 /** User-controlled enabled state. */
362 private boolean mEnabledByUser
= true;
363 /** Sensor event listener for this sensor. */
364 private final OurSensorEventListener mListener
= new OurSensorEventListener();
367 * Constructs MonitoredSensor instance, and register the listeners.
369 * @param sensor Sensor to monitor.
371 MonitoredSensor(Sensor sensor
) {
373 mEnabledByUser
= true;
375 // Set appropriate sensor name depending on the type. Unfortunately,
376 // we can't really use sensor.getName() here, since the value it
377 // returns (although resembles the purpose) is a bit vaguer than it
378 // should be. Also choose an appropriate format for the strings that
379 // display sensor's value.
380 switch (sensor
.getType()) {
381 case Sensor
.TYPE_ACCELEROMETER
:
382 mUiName
= "Accelerometer";
383 mTextFmt
= "%+.2f %+.2f %+.2f";
384 mEmulatorFriendlyName
= "acceleration";
386 case 9: // Sensor.TYPE_GRAVITY is missing in API 7
388 mTextFmt
= "%+.2f %+.2f %+.2f";
389 mEmulatorFriendlyName
= "gravity";
391 case Sensor
.TYPE_GYROSCOPE
:
392 mUiName
= "Gyroscope";
393 mTextFmt
= "%+.2f %+.2f %+.2f";
394 mEmulatorFriendlyName
= "gyroscope";
396 case Sensor
.TYPE_LIGHT
:
399 mEmulatorFriendlyName
= "light";
401 case 10: // Sensor.TYPE_LINEAR_ACCELERATION is missing in API 7
402 mUiName
= "Linear acceleration";
403 mTextFmt
= "%+.2f %+.2f %+.2f";
404 mEmulatorFriendlyName
= "linear-acceleration";
406 case Sensor
.TYPE_MAGNETIC_FIELD
:
407 mUiName
= "Magnetic field";
408 mTextFmt
= "%+.2f %+.2f %+.2f";
409 mEmulatorFriendlyName
= "magnetic-field";
411 case Sensor
.TYPE_ORIENTATION
:
412 mUiName
= "Orientation";
413 mTextFmt
= "%+03.0f %+03.0f %+03.0f";
414 mEmulatorFriendlyName
= "orientation";
416 case Sensor
.TYPE_PRESSURE
:
417 mUiName
= "Pressure";
419 mEmulatorFriendlyName
= "pressure";
421 case Sensor
.TYPE_PROXIMITY
:
422 mUiName
= "Proximity";
424 mEmulatorFriendlyName
= "proximity";
426 case 11: // Sensor.TYPE_ROTATION_VECTOR is missing in API 7
427 mUiName
= "Rotation";
428 mTextFmt
= "%+.2f %+.2f %+.2f";
429 mEmulatorFriendlyName
= "rotation";
431 case Sensor
.TYPE_TEMPERATURE
:
432 mUiName
= "Temperature";
434 mEmulatorFriendlyName
= "temperature";
437 mUiName
= "<Unknown>";
439 mEmulatorFriendlyName
= "unknown";
440 if (DEBUG
) Loge("Unknown sensor type " + mSensor
.getType() +
441 " for sensor " + mSensor
.getName());
447 * Get name for this sensor to display.
449 * @return Name for this sensor to display.
451 public String
getUiName() {
456 * Gets current sensor value to display.
458 * @return Current sensor value to display.
460 public String
getValue() {
461 if (mValue
== null) {
462 float[] values
= mValues
;
463 mValue
= String
.format(mTextFmt
, values
[0], values
[1], values
[2]);
465 return mValue
== null ?
"??" : mValue
;
469 * Checks if monitoring of this this sensor has been enabled by
472 * @return true if monitoring of this this sensor has been enabled by
473 * emulator, or false if emulator didn't enable this sensor.
475 public boolean isEnabledByEmulator() {
476 return mEnabledByEmulator
;
480 * Checks if monitoring of this this sensor has been enabled by user.
482 * @return true if monitoring of this this sensor has been enabled by
483 * user, or false if user didn't enable this sensor.
485 public boolean isEnabledByUser() {
486 return mEnabledByUser
;
490 * Handles checked state change for the associated CheckBox. If check
491 * box is checked we will register sensor change listener. If it is
492 * unchecked, we will unregister sensor change listener.
494 public void onCheckedChanged(boolean isChecked
) {
495 mEnabledByUser
= isChecked
;
506 * @return Sensor type as one of the Sensor.TYPE_XXX constants.
508 private int getType() {
509 return mSensor
.getType();
513 * Gets sensor's emulator-friendly name.
515 * @return Sensor's emulator-friendly name.
517 private String
getEmulatorFriendlyName() {
518 return mEmulatorFriendlyName
;
522 * Starts monitoring the sensor.
523 * NOTE: This method is called from outside of the UI thread.
525 private void startListening() {
526 if (mEnabledByEmulator
&& mEnabledByUser
) {
527 if (DEBUG
) Log
.d(TAG
, "+++ Sensor " + getEmulatorFriendlyName() + " is started.");
528 mSenMan
.registerListener(mListener
, mSensor
, SensorManager
.SENSOR_DELAY_FASTEST
);
533 * Stops monitoring the sensor.
534 * NOTE: This method is called from outside of the UI thread.
536 private void stopListening() {
537 if (DEBUG
) Log
.d(TAG
, "--- Sensor " + getEmulatorFriendlyName() + " is stopped.");
538 mSenMan
.unregisterListener(mListener
);
542 * Enables sensor events.
543 * NOTE: This method is called from outside of the UI thread.
545 private void enableSensor() {
546 if (DEBUG
) Log
.d(TAG
, ">>> Sensor " + getEmulatorFriendlyName() + " is enabled.");
547 mEnabledByEmulator
= true;
550 Message msg
= Message
.obtain();
551 msg
.what
= SENSOR_STATE_CHANGED
;
552 msg
.obj
= MonitoredSensor
.this;
553 notifyUiHandlers(msg
);
557 * Disables sensor events.
558 * NOTE: This method is called from outside of the UI thread.
560 private void disableSensor() {
561 if (DEBUG
) Log
.w(TAG
, "<<< Sensor " + getEmulatorFriendlyName() + " is disabled.");
562 mEnabledByEmulator
= false;
563 mValue
= "Disabled by emulator";
565 Message msg
= Message
.obtain();
566 msg
.what
= SENSOR_STATE_CHANGED
;
567 msg
.obj
= MonitoredSensor
.this;
568 notifyUiHandlers(msg
);
571 private class OurSensorEventListener
implements SensorEventListener
{
572 /** Last update's time-stamp in local thread millisecond time. */
573 private long mLastUpdateTS
= 0;
574 /** Last display update time-stamp. */
575 private long mLastDisplayTS
= 0;
576 /** Preallocated buffer for change notification message. */
577 private final ByteBuffer mChangeMsg
= ByteBuffer
.allocate(64);
580 * Handles "sensor changed" event.
581 * This is an implementation of the SensorEventListener interface.
584 public void onSensorChanged(SensorEvent event
) {
585 long now
= SystemClock
.elapsedRealtime();
588 if (mLastUpdateTS
!= 0) {
589 deltaMs
= now
- mLastUpdateTS
;
590 if (mUpdateTargetMs
> 0 && deltaMs
< mUpdateTargetMs
) {
591 // New sample is arriving too fast. Discard it.
596 // Format and post message for the emulator.
597 float[] values
= event
.values
;
598 final int len
= values
.length
;
600 mChangeMsg
.order(getEndian());
601 mChangeMsg
.position(0);
602 mChangeMsg
.putInt(getType());
603 mChangeMsg
.putFloat(values
[0]);
605 mChangeMsg
.putFloat(values
[1]);
607 mChangeMsg
.putFloat(values
[2]);
610 postMessage(ProtocolConstants
.SENSORS_SENSOR_EVENT
, mChangeMsg
);
612 // Computes average update time for this sensor and average globally.
613 if (mLastUpdateTS
!= 0) {
614 if (mGlobalAvgUpdateMs
!= 0) {
615 mGlobalAvgUpdateMs
= (mGlobalAvgUpdateMs
+ deltaMs
) / 2;
617 mGlobalAvgUpdateMs
= deltaMs
;
622 // Update the UI for the sensor, with a static throttling of 10 fps max.
623 if (hasUiHandler()) {
624 if (mLastDisplayTS
!= 0) {
625 long uiDeltaMs
= now
- mLastDisplayTS
;
626 if (uiDeltaMs
< 1000 / 4 /* 4fps in ms */) {
627 // Skip this UI update
631 mLastDisplayTS
= now
;
633 mValues
[0] = values
[0];
635 mValues
[1] = values
[1];
637 mValues
[2] = values
[2];
642 Message msg
= Message
.obtain();
643 msg
.what
= SENSOR_DISPLAY_MODIFIED
;
644 msg
.obj
= MonitoredSensor
.this;
645 notifyUiHandlers(msg
);
649 long now2
= SystemClock
.elapsedRealtime();
650 long processingTimeMs
= now2
- now
;
651 Log
.d(TAG
, String
.format("glob %d - local %d > target %d - processing %d -- %s",
652 mGlobalAvgUpdateMs
, deltaMs
, mUpdateTargetMs
, processingTimeMs
,
658 * Handles "sensor accuracy changed" event.
659 * This is an implementation of the SensorEventListener interface.
662 public void onAccuracyChanged(Sensor sensor
, int accuracy
) {
667 /***************************************************************************
669 **************************************************************************/
671 private void Loge(String log
) {
672 mService
.addError(log
);