SensorChannel.java revision 3c7c9c4b0b75d8817951a2c0764c6837335cd5e9
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
17package com.android.tools.sdkcontroller.handlers;
18
19import java.nio.ByteBuffer;
20import java.util.ArrayList;
21import java.util.List;
22
23import android.content.Context;
24import android.hardware.Sensor;
25import android.hardware.SensorEvent;
26import android.hardware.SensorEventListener;
27import android.hardware.SensorManager;
28import android.os.Message;
29import android.os.SystemClock;
30import android.util.Log;
31
32import com.android.tools.sdkcontroller.lib.Channel;
33import com.android.tools.sdkcontroller.lib.ProtocolConstants;
34import com.android.tools.sdkcontroller.service.ControllerService;
35
36/**
37 * Implements sensors emulation.
38 */
39public class SensorChannel extends Channel {
40
41    @SuppressWarnings("hiding")
42    private static String TAG = SensorChannel.class.getSimpleName();
43    @SuppressWarnings("hiding")
44    private static boolean DEBUG = false;
45    /**
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
50     * reasonable.
51     * Default value should match res/values/strings.xml > sensors_default_sample_rate.
52     */
53    private long mUpdateTargetMs = 1000/20; // 20 fps in milliseconds
54    /** Accumulates average update frequency. */
55    private long mGlobalAvgUpdateMs = 0;
56
57    /** Array containing monitored sensors. */
58    private final List<MonitoredSensor> mSensors = new ArrayList<MonitoredSensor>();
59    /** Sensor manager. */
60    private SensorManager mSenMan;
61
62    /*
63     * Messages exchanged with the UI.
64     */
65
66    /**
67     * Sensor "enabled by emulator" state has changed. Parameter {@code obj} is
68     * the {@link MonitoredSensor}.
69     */
70    public static final int SENSOR_STATE_CHANGED = 1;
71    /**
72     * Sensor display value has changed. Parameter {@code obj} is the
73     * {@link MonitoredSensor}.
74     */
75    public static final int SENSOR_DISPLAY_MODIFIED = 2;
76
77    /**
78     * Constructs SensorChannel instance.
79     *
80     * @param service Service context.
81     */
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);
87        int cur_index = 0;
88        for (int n = 0; n < sensors.size(); n++) {
89            Sensor avail_sensor = sensors.get(n);
90
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);
98                cur_index++;
99                mSensors.add(to_add);
100                if (DEBUG)
101                    Log.d(TAG, String.format(
102                            "Monitoring sensor #%02d: Name = '%s', Type = 0x%x",
103                            cur_index, def_sens.getName(), def_sens.getType()));
104            }
105        }
106    }
107
108    /**
109     * Returns the list of sensors found on the device.
110     * The list is computed once by {@link #SensorChannel(ControllerService)}.
111     *
112     * @return A non-null possibly-empty list of sensors.
113     */
114    public List<MonitoredSensor> getSensors() {
115        return mSensors;
116    }
117
118    /**
119     * Set the target update delay throttling per-sensor, in milliseconds.
120     * <p/>
121     * For example setting it to 1000/50 means that updates for a <em>given</em> sensor
122     * faster than 50 fps is discarded.
123     *
124     * @param updateTargetMs 0 to disable throttling, otherwise a > 0 millisecond minimum
125     *   between sensor updates.
126     */
127    public void setUpdateTargetMs(long updateTargetMs) {
128        mUpdateTargetMs = updateTargetMs;
129    }
130
131    /**
132     * Returns the actual average time in milliseconds between same-sensor updates.
133     *
134     * @return The actual average time in milliseconds between same-sensor updates or 0.
135     */
136    public long getActualUpdateMs() {
137        return mGlobalAvgUpdateMs;
138    }
139
140    /*
141     * Channel abstract implementation.
142     */
143
144    /**
145     * This method is invoked when this channel is fully connected with its
146     * counterpart in the emulator.
147     */
148    @Override
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.
152        enable();
153    }
154
155    /**
156     * This method is invoked when this channel loses connection with its
157     * counterpart in the emulator.
158     */
159    @Override
160    public void onEmulatorDisconnected() {
161        // Stop sensor event callbacks.
162        stopSensors();
163    }
164
165    /**
166     * A query has been received from the emulator.
167     *
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.
172     */
173    @Override
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());
190                    resp.put(name);
191                    resp.put((byte) 0);
192                }
193                // Terminating entry contains single -1 integer.
194                resp = ExpandIf(resp, 4);
195                resp.putInt(-1);
196                sendQueryResponse(query_id, resp);
197                return;
198
199            default:
200                Loge("Unknown query " + query_type);
201                return;
202        }
203    }
204
205    /**
206     * A message has been received from the emulator.
207     *
208     * @param msg_type Message type.
209     * @param msg_data Packet received from the emulator.
210     */
211    @Override
212    public void onEmulatorMessage(int msg_type, ByteBuffer msg_data) {
213        switch (msg_type) {
214            case ProtocolConstants.SENSORS_START:
215                Log.v(TAG, "Starting sensors emulation.");
216                startSensors();
217                break;
218            case ProtocolConstants.SENSORS_STOP:
219                Log.v(TAG, "Stopping sensors emulation.");
220                stopSensors();
221                break;
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);
226                break;
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);
231                break;
232            default:
233                Loge("Unknown message type " + msg_type);
234                break;
235        }
236    }
237
238    /**
239     * Handles 'enable' message.
240     *
241     * @param name Emulator-friendly name of a sensor to enable, or "all" to
242     *            enable all sensors.
243     */
244    private void onEnableSensor(String name) {
245        if (name.contentEquals("all")) {
246            // Enable all sensors.
247            for (MonitoredSensor sensor : mSensors) {
248                sensor.enableSensor();
249            }
250        } else {
251            // Lookup sensor by emulator-friendly name.
252            final MonitoredSensor sensor = getSensorByEFN(name);
253            if (sensor != null) {
254                sensor.enableSensor();
255            }
256        }
257    }
258
259    /**
260     * Handles 'disable' message.
261     *
262     * @param name Emulator-friendly name of a sensor to disable, or "all" to
263     *            disable all sensors.
264     */
265    private void onDisableSensor(String name) {
266        if (name.contentEquals("all")) {
267            // Disable all sensors.
268            for (MonitoredSensor sensor : mSensors) {
269                sensor.disableSensor();
270            }
271        } else {
272            // Lookup sensor by emulator-friendly name.
273            MonitoredSensor sensor = getSensorByEFN(name);
274            if (sensor != null) {
275                sensor.disableSensor();
276            }
277        }
278    }
279
280    /**
281     * Start listening to all monitored sensors.
282     */
283    private void startSensors() {
284        for (MonitoredSensor sensor : mSensors) {
285            sensor.startListening();
286        }
287    }
288
289    /**
290     * Stop listening to all monitored sensors.
291     */
292    private void stopSensors() {
293        for (MonitoredSensor sensor : mSensors) {
294            sensor.stopListening();
295        }
296    }
297
298    /***************************************************************************
299     * Internals
300     **************************************************************************/
301
302    /**
303     * Checks if a sensor for the given type is already monitored.
304     *
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.
308     */
309    private boolean isSensorTypeAlreadyMonitored(int type) {
310        for (MonitoredSensor sensor : mSensors) {
311            if (sensor.getType() == type) {
312                return true;
313            }
314        }
315        return false;
316    }
317
318    /**
319     * Looks up a monitored sensor by its emulator-friendly name.
320     *
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
323     *         found.
324     */
325    private MonitoredSensor getSensorByEFN(String name) {
326        for (MonitoredSensor sensor : mSensors) {
327            if (sensor.mEmulatorFriendlyName.contentEquals(name)) {
328                return sensor;
329            }
330        }
331        return null;
332    }
333
334    /**
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.
341     */
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];
355        /**
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.
359         */
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();
365
366        /**
367         * Constructs MonitoredSensor instance, and register the listeners.
368         *
369         * @param sensor Sensor to monitor.
370         */
371        MonitoredSensor(Sensor sensor) {
372            mSensor = sensor;
373            mEnabledByUser = true;
374
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";
385                    break;
386                case 9: // Sensor.TYPE_GRAVITY is missing in API 7
387                    mUiName = "Gravity";
388                    mTextFmt = "%+.2f %+.2f %+.2f";
389                    mEmulatorFriendlyName = "gravity";
390                    break;
391                case Sensor.TYPE_GYROSCOPE:
392                    mUiName = "Gyroscope";
393                    mTextFmt = "%+.2f %+.2f %+.2f";
394                    mEmulatorFriendlyName = "gyroscope";
395                    break;
396                case Sensor.TYPE_LIGHT:
397                    mUiName = "Light";
398                    mTextFmt = "%.0f";
399                    mEmulatorFriendlyName = "light";
400                    break;
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";
405                    break;
406                case Sensor.TYPE_MAGNETIC_FIELD:
407                    mUiName = "Magnetic field";
408                    mTextFmt = "%+.2f %+.2f %+.2f";
409                    mEmulatorFriendlyName = "magnetic-field";
410                    break;
411                case Sensor.TYPE_ORIENTATION:
412                    mUiName = "Orientation";
413                    mTextFmt = "%+03.0f %+03.0f %+03.0f";
414                    mEmulatorFriendlyName = "orientation";
415                    break;
416                case Sensor.TYPE_PRESSURE:
417                    mUiName = "Pressure";
418                    mTextFmt = "%.0f";
419                    mEmulatorFriendlyName = "pressure";
420                    break;
421                case Sensor.TYPE_PROXIMITY:
422                    mUiName = "Proximity";
423                    mTextFmt = "%.0f";
424                    mEmulatorFriendlyName = "proximity";
425                    break;
426                case 11: // Sensor.TYPE_ROTATION_VECTOR is missing in API 7
427                    mUiName = "Rotation";
428                    mTextFmt = "%+.2f %+.2f %+.2f";
429                    mEmulatorFriendlyName = "rotation";
430                    break;
431                case Sensor.TYPE_TEMPERATURE:
432                    mUiName = "Temperature";
433                    mTextFmt = "%.0f";
434                    mEmulatorFriendlyName = "temperature";
435                    break;
436                default:
437                    mUiName = "<Unknown>";
438                    mTextFmt = "N/A";
439                    mEmulatorFriendlyName = "unknown";
440                    if (DEBUG) Loge("Unknown sensor type " + mSensor.getType() +
441                            " for sensor " + mSensor.getName());
442                    break;
443            }
444        }
445
446        /**
447         * Get name for this sensor to display.
448         *
449         * @return Name for this sensor to display.
450         */
451        public String getUiName() {
452            return mUiName;
453        }
454
455        /**
456         * Gets current sensor value to display.
457         *
458         * @return Current sensor value to display.
459         */
460        public String getValue() {
461            if (mValue == null) {
462                float[] values = mValues;
463                mValue = String.format(mTextFmt, values[0], values[1], values[2]);
464            }
465            return mValue == null ? "??" : mValue;
466        }
467
468        /**
469         * Checks if monitoring of this this sensor has been enabled by
470         * emulator.
471         *
472         * @return true if monitoring of this this sensor has been enabled by
473         *         emulator, or false if emulator didn't enable this sensor.
474         */
475        public boolean isEnabledByEmulator() {
476            return mEnabledByEmulator;
477        }
478
479        /**
480         * Checks if monitoring of this this sensor has been enabled by user.
481         *
482         * @return true if monitoring of this this sensor has been enabled by
483         *         user, or false if user didn't enable this sensor.
484         */
485        public boolean isEnabledByUser() {
486            return mEnabledByUser;
487        }
488
489        /**
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.
493         */
494        public void onCheckedChanged(boolean isChecked) {
495            mEnabledByUser = isChecked;
496            if (isChecked) {
497                startListening();
498            } else {
499                stopListening();
500            }
501        }
502
503        /**
504         * Gets sensor type.
505         *
506         * @return Sensor type as one of the Sensor.TYPE_XXX constants.
507         */
508        private int getType() {
509            return mSensor.getType();
510        }
511
512        /**
513         * Gets sensor's emulator-friendly name.
514         *
515         * @return Sensor's emulator-friendly name.
516         */
517        private String getEmulatorFriendlyName() {
518            return mEmulatorFriendlyName;
519        }
520
521        /**
522         * Starts monitoring the sensor.
523         * NOTE: This method is called from outside of the UI thread.
524         */
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);
529            }
530        }
531
532        /**
533         * Stops monitoring the sensor.
534         * NOTE: This method is called from outside of the UI thread.
535         */
536        private void stopListening() {
537            if (DEBUG) Log.d(TAG, "--- Sensor " + getEmulatorFriendlyName() + " is stopped.");
538            mSenMan.unregisterListener(mListener);
539        }
540
541        /**
542         * Enables sensor events.
543         * NOTE: This method is called from outside of the UI thread.
544         */
545        private void enableSensor() {
546            if (DEBUG) Log.d(TAG, ">>> Sensor " + getEmulatorFriendlyName() + " is enabled.");
547            mEnabledByEmulator = true;
548            mValue = null;
549
550            Message msg = Message.obtain();
551            msg.what = SENSOR_STATE_CHANGED;
552            msg.obj = MonitoredSensor.this;
553            notifyUiHandlers(msg);
554        }
555
556        /**
557         * Disables sensor events.
558         * NOTE: This method is called from outside of the UI thread.
559         */
560        private void disableSensor() {
561            if (DEBUG) Log.w(TAG, "<<< Sensor " + getEmulatorFriendlyName() + " is disabled.");
562            mEnabledByEmulator = false;
563            mValue = "Disabled by emulator";
564
565            Message msg = Message.obtain();
566            msg.what = SENSOR_STATE_CHANGED;
567            msg.obj = MonitoredSensor.this;
568            notifyUiHandlers(msg);
569        }
570
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);
578
579            /**
580             * Handles "sensor changed" event.
581             * This is an implementation of the SensorEventListener interface.
582             */
583            @Override
584            public void onSensorChanged(SensorEvent event) {
585                long now = SystemClock.elapsedRealtime();
586
587                long deltaMs = 0;
588                if (mLastUpdateTS != 0) {
589                    deltaMs = now - mLastUpdateTS;
590                    if (mUpdateTargetMs > 0 && deltaMs < mUpdateTargetMs) {
591                        // New sample is arriving too fast. Discard it.
592                        return;
593                    }
594                }
595
596                // Format and post message for the emulator.
597                float[] values = event.values;
598                final int len = values.length;
599
600                mChangeMsg.order(getEndian());
601                mChangeMsg.position(0);
602                mChangeMsg.putInt(getType());
603                mChangeMsg.putFloat(values[0]);
604                if (len > 1) {
605                    mChangeMsg.putFloat(values[1]);
606                    if (len > 2) {
607                        mChangeMsg.putFloat(values[2]);
608                    }
609                }
610                postMessage(ProtocolConstants.SENSORS_SENSOR_EVENT, mChangeMsg);
611
612                // Computes average update time for this sensor and average globally.
613                if (mLastUpdateTS != 0) {
614                    if (mGlobalAvgUpdateMs != 0) {
615                        mGlobalAvgUpdateMs = (mGlobalAvgUpdateMs + deltaMs) / 2;
616                    } else {
617                        mGlobalAvgUpdateMs = deltaMs;
618                    }
619                }
620                mLastUpdateTS = now;
621
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
628                            return;
629                        }
630                    }
631                    mLastDisplayTS = now;
632
633                    mValues[0] = values[0];
634                    if (len > 1) {
635                        mValues[1] = values[1];
636                        if (len > 2) {
637                            mValues[2] = values[2];
638                        }
639                    }
640                    mValue = null;
641
642                    Message msg = Message.obtain();
643                    msg.what = SENSOR_DISPLAY_MODIFIED;
644                    msg.obj = MonitoredSensor.this;
645                    notifyUiHandlers(msg);
646                }
647
648                if (DEBUG) {
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,
653                            mSensor.getName()));
654                }
655            }
656
657            /**
658             * Handles "sensor accuracy changed" event.
659             * This is an implementation of the SensorEventListener interface.
660             */
661            @Override
662            public void onAccuracyChanged(Sensor sensor, int accuracy) {
663            }
664        }
665    } // MonitoredSensor
666
667    /***************************************************************************
668     * Logging wrappers
669     **************************************************************************/
670
671    private void Loge(String log) {
672        mService.addError(log);
673        Log.e(TAG, log);
674    }
675}
676