• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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