1 /* 2 * Copyright (C) 2012 The Android Open Source Project 3 * 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 6 * the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 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 14 * the License. 15 */ 16 17 package com.android.tools.sdkcontroller.handlers; 18 19 import java.util.ArrayList; 20 import java.util.List; 21 22 import android.content.Context; 23 import android.hardware.Sensor; 24 import android.hardware.SensorEvent; 25 import android.hardware.SensorEventListener; 26 import android.hardware.SensorManager; 27 import android.os.Message; 28 import android.os.SystemClock; 29 import android.util.Log; 30 31 import com.android.tools.sdkcontroller.lib.EmulatorConnection; 32 33 34 public class SensorsHandler extends BaseHandler { 35 36 @SuppressWarnings("hiding") 37 private static String TAG = SensorsHandler.class.getSimpleName(); 38 @SuppressWarnings("hiding") 39 private static boolean DEBUG = false; 40 /** 41 * The target update time per sensor. Ignored if 0 or negative. 42 * Sensor updates that arrive faster than this delay are ignored. 43 * Ideally the emulator can be updated at up to 50 fps, however 44 * for average power devices something like 20 fps is more 45 * reasonable. 46 * Default value should match res/values/strings.xml > sensors_default_sample_rate. 47 */ 48 private long mUpdateTargetMs = 1000/20; // 20 fps in milliseconds 49 private long mGlobalAvgUpdateMs = 0; 50 51 52 /** 53 * Sensor "enabled by emulator" state has changed. 54 * Parameter {@code obj} is the {@link MonitoredSensor}. 55 */ 56 public static final int SENSOR_STATE_CHANGED = 1; 57 /** 58 * Sensor display value has changed. 59 * Parameter {@code obj} is the {@link MonitoredSensor}. 60 */ 61 public static final int SENSOR_DISPLAY_MODIFIED = 2; 62 63 /** Array containing monitored sensors. */ 64 private final List<MonitoredSensor> mSensors = new ArrayList<MonitoredSensor>(); 65 private SensorManager mSenMan; 66 SensorsHandler()67 public SensorsHandler() { 68 super(HandlerType.Sensor, EmulatorConnection.SENSORS_PORT); 69 } 70 71 /** 72 * Returns the list of sensors found on the device. 73 * The list is computed once by {@link #onStart(EmulatorConnection, Context)}. 74 * 75 * @return A non-null possibly-empty list of sensors. 76 */ getSensors()77 public List<MonitoredSensor> getSensors() { 78 return mSensors; 79 } 80 81 /** 82 * Set the target update delay throttling per-sensor, in milliseconds. 83 * <p/> 84 * For example setting it to 1000/50 means that updates for a <em>given</em> sensor 85 * faster than 50 fps is discarded. 86 * 87 * @param updateTargetMs 0 to disable throttling, otherwise a > 0 millisecond minimum 88 * between sensor updates. 89 */ setUpdateTargetMs(long updateTargetMs)90 public void setUpdateTargetMs(long updateTargetMs) { 91 mUpdateTargetMs = updateTargetMs; 92 } 93 94 /** 95 * Returns the actual average time in milliseconds between same-sensor updates. 96 * 97 * @return The actual average time in milliseconds between same-sensor updates or 0. 98 */ getActualUpdateMs()99 public long getActualUpdateMs() { 100 return mGlobalAvgUpdateMs; 101 } 102 103 @Override onStart(EmulatorConnection connection, Context context)104 public void onStart(EmulatorConnection connection, Context context) { 105 super.onStart(connection, context); 106 107 // Iterate through the available sensors, adding them to the array. 108 SensorManager sm = (SensorManager) context.getSystemService(Context.SENSOR_SERVICE); 109 mSenMan = sm; 110 List<Sensor> sensors = sm.getSensorList(Sensor.TYPE_ALL); 111 int cur_index = 0; 112 for (int n = 0; n < sensors.size(); n++) { 113 Sensor avail_sensor = sensors.get(n); 114 115 // There can be multiple sensors of the same type. We need only one. 116 if (!isSensorTypeAlreadyMonitored(avail_sensor.getType())) { 117 // The first sensor we've got for the given type is not 118 // necessarily the right one. So, use the default sensor 119 // for the given type. 120 Sensor def_sens = sm.getDefaultSensor(avail_sensor.getType()); 121 MonitoredSensor to_add = new MonitoredSensor(def_sens); 122 cur_index++; 123 mSensors.add(to_add); 124 if (DEBUG) Log.d(TAG, String.format( 125 "Monitoring sensor #%02d: Name = '%s', Type = 0x%x", 126 cur_index, def_sens.getName(), def_sens.getType())); 127 } 128 } 129 } 130 131 @Override onStop()132 public void onStop() { 133 stopSensors(); 134 super.onStop(); 135 } 136 137 /** 138 * Called when a query is received from the emulator. NOTE: This method is 139 * called from the I/O loop. 140 * 141 * @param query Name of the query received from the emulator. The allowed 142 * queries are: 'list' - Lists sensors that are monitored by this 143 * application. The application replies to this command with a 144 * string: 'List:<name1>\n<name2>\n...<nameN>\n\0" 'start' - 145 * Starts monitoring sensors. There is no reply for this command. 146 * 'stop' - Stops monitoring sensors. There is no reply for this 147 * command. 'enable:<sensor|all> - Enables notifications for a 148 * sensor / all sensors. 'disable:<sensor|all> - Disables 149 * notifications for a sensor / all sensors. 150 * @param param Query parameters. 151 * @return Zero-terminated reply string. String must be formatted as such: 152 * "ok|ko[:reply data]" 153 */ 154 @Override onEmulatorQuery(String query, String param)155 public String onEmulatorQuery(String query, String param) { 156 if (query.contentEquals("list")) { 157 return onQueryList(); 158 } else if (query.contentEquals("start")) { 159 return onQueryStart(); 160 } else if (query.contentEquals("stop")) { 161 return onQueryStop(); 162 } else if (query.contentEquals("enable")) { 163 return onQueryEnable(param); 164 } else if (query.contentEquals("disable")) { 165 return onQueryDisable(param); 166 } else { 167 Log.e(TAG, "Unknown query " + query + "(" + param + ")"); 168 return "ko:Query is unknown\0"; 169 } 170 } 171 172 /** 173 * Called when a BLOB query is received from the emulator. NOTE: This method 174 * is called from the I/O loop, so all communication with the emulator will 175 * be "on hold" until this method returns. 176 * 177 * @param array contains BLOB data for the query. 178 * @return Zero-terminated reply string. String must be formatted as such: 179 * "ok|ko[:reply data]" 180 */ 181 @Override onEmulatorBlobQuery(byte[] array)182 public String onEmulatorBlobQuery(byte[] array) { 183 return "ko:Unexpected\0"; 184 } 185 186 /*************************************************************************** 187 * Query handlers 188 **************************************************************************/ 189 190 /** 191 * Handles 'list' query. 192 * 193 * @return List of emulator-friendly names for sensors that are available on 194 * the device. 195 */ onQueryList()196 private String onQueryList() { 197 // List monitored sensors. 198 String list = "ok:"; 199 for (MonitoredSensor sensor : mSensors) { 200 list += sensor.getEmulatorFriendlyName(); 201 list += "\n"; 202 } 203 list += '\0'; // Response must end with zero-terminator. 204 return list; 205 } 206 207 /** 208 * Handles 'start' query. 209 * 210 * @return Empty string. This is a "command" query that doesn't assume any 211 * response. 212 */ onQueryStart()213 private String onQueryStart() { 214 startSensors(); 215 return "ok\0"; 216 } 217 218 /** 219 * Handles 'stop' query. 220 * 221 * @return Empty string. This is a "command" query that doesn't assume any 222 * response. 223 */ onQueryStop()224 private String onQueryStop() { 225 stopSensors(); 226 return "ok\0"; 227 } 228 229 /** 230 * Handles 'enable' query. 231 * 232 * @param param Sensor selector: - all Enables all available sensors, or - 233 * <name> Emulator-friendly name of a sensor to enable. 234 * @return "ok" / "ko": success / failure. 235 */ onQueryEnable(String param)236 private String onQueryEnable(String param) { 237 if (param.contentEquals("all")) { 238 // Enable all sensors. 239 for (MonitoredSensor sensor : mSensors) { 240 sensor.enableSensor(); 241 } 242 return "ok\0"; 243 } 244 245 // Lookup sensor by emulator-friendly name. 246 MonitoredSensor sensor = getSensorByEFN(param); 247 if (sensor != null) { 248 sensor.enableSensor(); 249 return "ok\0"; 250 } else { 251 return "ko:Sensor not found\0"; 252 } 253 } 254 255 /** 256 * Handles 'disable' query. 257 * 258 * @param param Sensor selector: - all Disables all available sensors, or - 259 * <name> Emulator-friendly name of a sensor to disable. 260 * @return "ok" / "ko": success / failure. 261 */ onQueryDisable(String param)262 private String onQueryDisable(String param) { 263 if (param.contentEquals("all")) { 264 // Disable all sensors. 265 for (MonitoredSensor sensor : mSensors) { 266 sensor.disableSensor(); 267 } 268 return "ok\0"; 269 } 270 271 // Lookup sensor by emulator-friendly name. 272 MonitoredSensor sensor = getSensorByEFN(param); 273 if (sensor != null) { 274 sensor.disableSensor(); 275 return "ok\0"; 276 } else { 277 return "ko:Sensor not found\0"; 278 } 279 } 280 281 /*************************************************************************** 282 * Internals 283 **************************************************************************/ 284 285 /** 286 * Start listening to all monitored sensors. 287 */ startSensors()288 private void startSensors() { 289 for (MonitoredSensor sensor : mSensors) { 290 sensor.startListening(); 291 } 292 } 293 294 /** 295 * Stop listening to all monitored sensors. 296 */ stopSensors()297 private void stopSensors() { 298 for (MonitoredSensor sensor : mSensors) { 299 sensor.stopListening(); 300 } 301 } 302 303 /** 304 * Checks if a sensor for the given type is already monitored. 305 * 306 * @param type Sensor type (one of the Sensor.TYPE_XXX constants) 307 * @return true if a sensor for the given type is already monitored, or 308 * false if the sensor is not monitored. 309 */ isSensorTypeAlreadyMonitored(int type)310 private boolean isSensorTypeAlreadyMonitored(int type) { 311 for (MonitoredSensor sensor : mSensors) { 312 if (sensor.getType() == type) { 313 return true; 314 } 315 } 316 return false; 317 } 318 319 /** 320 * Looks up a monitored sensor by its emulator-friendly name. 321 * 322 * @param name Emulator-friendly name to look up the monitored sensor for. 323 * @return Monitored sensor for the fiven name, or null if sensor was not 324 * found. 325 */ getSensorByEFN(String name)326 private MonitoredSensor getSensorByEFN(String name) { 327 for (MonitoredSensor sensor : mSensors) { 328 if (sensor.mEmulatorFriendlyName.contentEquals(name)) { 329 return sensor; 330 } 331 } 332 return null; 333 } 334 335 /** 336 * Encapsulates a sensor that is being monitored. To monitor sensor changes 337 * each monitored sensor registers with sensor manager as a sensor listener. 338 * To control sensor monitoring from the UI, each monitored sensor has two 339 * UI controls associated with it: - A check box (named after sensor) that 340 * can be used to enable, or disable listening to the sensor changes. - A 341 * text view where current sensor value is displayed. 342 */ 343 public class MonitoredSensor { 344 /** Sensor to monitor. */ 345 private final Sensor mSensor; 346 /** The sensor name to display in the UI. */ 347 private String mUiName = ""; 348 /** Text view displaying the value of the sensor. */ 349 private String mValue = null; 350 /** Emulator-friendly name for the sensor. */ 351 private String mEmulatorFriendlyName; 352 /** Formats string to show in the TextView. */ 353 private String mTextFmt; 354 private int mExpectedLen; 355 private int mNbValues = 0; 356 private float[] mValues = new float[3]; 357 /** 358 * Enabled state. This state is controlled by the emulator, that 359 * maintains its own list of sensors. So, if a sensor is missing, or is 360 * disabled in the emulator, it should be disabled in this application. 361 */ 362 private boolean mEnabledByEmulator = false; 363 /** User-controlled enabled state. */ 364 private boolean mEnabledByUser = true; 365 private final OurSensorEventListener mListener = new OurSensorEventListener(); 366 367 /** 368 * Constructs MonitoredSensor instance, and register the listeners. 369 * 370 * @param sensor Sensor to monitor. 371 */ MonitoredSensor(Sensor sensor)372 MonitoredSensor(Sensor sensor) { 373 mSensor = sensor; 374 mEnabledByUser = true; 375 376 // Set appropriate sensor name depending on the type. Unfortunately, 377 // we can't really use sensor.getName() here, since the value it 378 // returns (although resembles the purpose) is a bit vaguer than it 379 // should be. Also choose an appropriate format for the strings that 380 // display sensor's value, and strings that are sent to the 381 // emulator. 382 switch (sensor.getType()) { 383 case Sensor.TYPE_ACCELEROMETER: 384 mUiName = "Accelerometer"; 385 // 3 floats. 386 mTextFmt = "%+.2f %+.2f %+.2f"; 387 mEmulatorFriendlyName = "acceleration"; 388 mExpectedLen = 3; 389 break; 390 case 9: // Sensor.TYPE_GRAVITY is missing in API 7 391 // 3 floats. 392 mUiName = "Gravity"; 393 mTextFmt = "%+.2f %+.2f %+.2f"; 394 mEmulatorFriendlyName = "gravity"; 395 mExpectedLen = 3; 396 break; 397 case Sensor.TYPE_GYROSCOPE: 398 mUiName = "Gyroscope"; 399 // 3 floats. 400 mTextFmt = "%+.2f %+.2f %+.2f"; 401 mEmulatorFriendlyName = "gyroscope"; 402 mExpectedLen = 3; 403 break; 404 case Sensor.TYPE_LIGHT: 405 mUiName = "Light"; 406 // 1 integer. 407 mTextFmt = "%.0f"; 408 mEmulatorFriendlyName = "light"; 409 mExpectedLen = 1; 410 break; 411 case 10: // Sensor.TYPE_LINEAR_ACCELERATION is missing in API 7 412 mUiName = "Linear acceleration"; 413 // 3 floats. 414 mTextFmt = "%+.2f %+.2f %+.2f"; 415 mEmulatorFriendlyName = "linear-acceleration"; 416 mExpectedLen = 3; 417 break; 418 case Sensor.TYPE_MAGNETIC_FIELD: 419 mUiName = "Magnetic field"; 420 // 3 floats. 421 mTextFmt = "%+.2f %+.2f %+.2f"; 422 mEmulatorFriendlyName = "magnetic-field"; 423 mExpectedLen = 3; 424 break; 425 case Sensor.TYPE_ORIENTATION: 426 mUiName = "Orientation"; 427 // 3 integers. 428 mTextFmt = "%+03.0f %+03.0f %+03.0f"; 429 mEmulatorFriendlyName = "orientation"; 430 mExpectedLen = 3; 431 break; 432 case Sensor.TYPE_PRESSURE: 433 mUiName = "Pressure"; 434 // 1 integer. 435 mTextFmt = "%.0f"; 436 mEmulatorFriendlyName = "pressure"; 437 mExpectedLen = 1; 438 break; 439 case Sensor.TYPE_PROXIMITY: 440 mUiName = "Proximity"; 441 // 1 integer. 442 mTextFmt = "%.0f"; 443 mEmulatorFriendlyName = "proximity"; 444 mExpectedLen = 1; 445 break; 446 case 11: // Sensor.TYPE_ROTATION_VECTOR is missing in API 7 447 mUiName = "Rotation"; 448 // 3 floats. 449 mTextFmt = "%+.2f %+.2f %+.2f"; 450 mEmulatorFriendlyName = "rotation"; 451 mExpectedLen = 3; 452 break; 453 case Sensor.TYPE_TEMPERATURE: 454 mUiName = "Temperature"; 455 // 1 integer. 456 mTextFmt = "%.0f"; 457 mEmulatorFriendlyName = "temperature"; 458 mExpectedLen = 1; 459 break; 460 default: 461 mUiName = "<Unknown>"; 462 mTextFmt = "N/A"; 463 mEmulatorFriendlyName = "unknown"; 464 mExpectedLen = 0; 465 if (DEBUG) Log.e(TAG, "Unknown sensor type " + mSensor.getType() + 466 " for sensor " + mSensor.getName()); 467 break; 468 } 469 } 470 getUiName()471 public String getUiName() { 472 return mUiName; 473 } 474 getValue()475 public String getValue() { 476 String val = mValue; 477 478 if (val == null) { 479 int len = mNbValues; 480 float[] values = mValues; 481 if (len == 3) { 482 val = String.format(mTextFmt, values[0], values[1],values[2]); 483 } else if (len == 2) { 484 val = String.format(mTextFmt, values[0], values[1]); 485 } else if (len == 1) { 486 val = String.format(mTextFmt, values[0]); 487 } 488 mValue = val; 489 } 490 491 return val == null ? "??" : val; 492 } 493 isEnabledByEmulator()494 public boolean isEnabledByEmulator() { 495 return mEnabledByEmulator; 496 } 497 isEnabledByUser()498 public boolean isEnabledByUser() { 499 return mEnabledByUser; 500 } 501 502 /** 503 * Handles checked state change for the associated CheckBox. If check 504 * box is checked we will register sensor change listener. If it is 505 * unchecked, we will unregister sensor change listener. 506 */ onCheckedChanged(boolean isChecked)507 public void onCheckedChanged(boolean isChecked) { 508 mEnabledByUser = isChecked; 509 if (isChecked) { 510 startListening(); 511 } else { 512 stopListening(); 513 } 514 } 515 516 // --------- 517 518 /** 519 * Gets sensor type. 520 * 521 * @return Sensor type as one of the Sensor.TYPE_XXX constants. 522 */ getType()523 private int getType() { 524 return mSensor.getType(); 525 } 526 527 /** 528 * Gets sensor's emulator-friendly name. 529 * 530 * @return Sensor's emulator-friendly name. 531 */ getEmulatorFriendlyName()532 private String getEmulatorFriendlyName() { 533 return mEmulatorFriendlyName; 534 } 535 536 /** 537 * Starts monitoring the sensor. 538 * NOTE: This method is called from outside of the UI thread. 539 */ startListening()540 private void startListening() { 541 if (mEnabledByEmulator && mEnabledByUser) { 542 if (DEBUG) Log.d(TAG, "+++ Sensor " + getEmulatorFriendlyName() + " is started."); 543 mSenMan.registerListener(mListener, mSensor, SensorManager.SENSOR_DELAY_FASTEST); 544 } 545 } 546 547 /** 548 * Stops monitoring the sensor. 549 * NOTE: This method is called from outside of the UI thread. 550 */ stopListening()551 private void stopListening() { 552 if (DEBUG) Log.d(TAG, "--- Sensor " + getEmulatorFriendlyName() + " is stopped."); 553 mSenMan.unregisterListener(mListener); 554 } 555 556 /** 557 * Enables sensor events. 558 * NOTE: This method is called from outside of the UI thread. 559 */ enableSensor()560 private void enableSensor() { 561 if (DEBUG) Log.d(TAG, ">>> Sensor " + getEmulatorFriendlyName() + " is enabled."); 562 mEnabledByEmulator = true; 563 mNbValues = 0; 564 mValue = null; 565 566 Message msg = Message.obtain(); 567 msg.what = SENSOR_STATE_CHANGED; 568 msg.obj = MonitoredSensor.this; 569 notifyUiHandlers(msg); 570 } 571 572 /** 573 * Disables sensor events. 574 * NOTE: This method is called from outside of the UI thread. 575 */ disableSensor()576 private void disableSensor() { 577 if (DEBUG) Log.w(TAG, "<<< Sensor " + getEmulatorFriendlyName() + " is disabled."); 578 mEnabledByEmulator = false; 579 mValue = "Disabled by emulator"; 580 581 Message msg = Message.obtain(); 582 msg.what = SENSOR_STATE_CHANGED; 583 msg.obj = MonitoredSensor.this; 584 notifyUiHandlers(msg); 585 } 586 587 private class OurSensorEventListener implements SensorEventListener { 588 /** Last update's time-stamp in local thread millisecond time. */ 589 private long mLastUpdateTS; 590 /** Last display update time-stamp. */ 591 private long mLastDisplayTS; 592 private final StringBuilder mTempStr = new StringBuilder(); 593 594 /** 595 * Handles "sensor changed" event. 596 * This is an implementation of the SensorEventListener interface. 597 */ 598 @Override onSensorChanged(SensorEvent event)599 public void onSensorChanged(SensorEvent event) { 600 long now = SystemClock.currentThreadTimeMillis(); 601 602 long deltaMs = 0; 603 if (mLastUpdateTS != 0) { 604 deltaMs = now - mLastUpdateTS; 605 if (mUpdateTargetMs > 0 && deltaMs < mUpdateTargetMs) { 606 // New sample is arriving too fast. Discard it. 607 return; 608 } 609 } 610 611 // Format message that will be sent to the emulator. 612 float[] values = event.values; 613 final int len = values.length; 614 615 // A 3printfs with 3 * %g takes around 9-15 ms on an ADP2, or 3-4 ms on a GN. 616 // However doing 3 * StringBuilder.append(float) takes < ~1 ms on ADP2. 617 StringBuilder sb = mTempStr; 618 sb.setLength(0); 619 sb.append(mEmulatorFriendlyName); 620 621 if (len != mExpectedLen) { 622 Log.e(TAG, "Unexpected number of values " + len 623 + " in onSensorChanged for sensor " + mSensor.getName()); 624 return; 625 } else { 626 sb.append(':').append(values[0]); 627 if (len > 1) { 628 sb.append(':').append(values[1]); 629 if (len > 2) { 630 sb.append(':').append(values[2]); 631 } 632 } 633 } 634 sb.append('\0'); 635 sendEventToEmulator(sb.toString()); 636 637 // Computes average update time for this sensor and average globally. 638 if (mLastUpdateTS != 0) { 639 if (mGlobalAvgUpdateMs != 0) { 640 mGlobalAvgUpdateMs = (mGlobalAvgUpdateMs + deltaMs) / 2; 641 } else { 642 mGlobalAvgUpdateMs = deltaMs; 643 } 644 } 645 mLastUpdateTS = now; 646 647 // Update the UI for the sensor, with a static throttling of 10 fps max. 648 if (hasUiHandler()) { 649 if (mLastDisplayTS != 0) { 650 long uiDeltaMs = now - mLastDisplayTS; 651 if (uiDeltaMs < 1000/4 /*4fps in ms*/) { 652 // Skip this UI update 653 return; 654 } 655 } 656 mLastDisplayTS = now; 657 658 mNbValues = len; 659 mValues[0] = values[0]; 660 if (len > 1) { 661 mValues[1] = values[1]; 662 if (len > 2) { 663 mValues[2] = values[2]; 664 } 665 } 666 mValue = null; 667 668 Message msg = Message.obtain(); 669 msg.what = SENSOR_DISPLAY_MODIFIED; 670 msg.obj = MonitoredSensor.this; 671 notifyUiHandlers(msg); 672 } 673 674 if (DEBUG) { 675 long now2 = SystemClock.currentThreadTimeMillis(); 676 long processingTimeMs = now2 - now; 677 Log.d(TAG, String.format("glob %d - local %d > target %d - processing %d -- %s", 678 mGlobalAvgUpdateMs, deltaMs, mUpdateTargetMs, processingTimeMs, 679 mSensor.getName())); 680 } 681 } 682 683 /** 684 * Handles "sensor accuracy changed" event. This is an implementation of 685 * the SensorEventListener interface. 686 */ 687 @Override onAccuracyChanged(Sensor sensor, int accuracy)688 public void onAccuracyChanged(Sensor sensor, int accuracy) { 689 } 690 } 691 } // MonitoredSensor 692 693 } 694