1 /*
2  * Copyright (C) 2016 Google Inc.
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.googlecode.android_scripting.facade;
18 
19 import android.content.Context;
20 import android.hardware.Sensor;
21 import android.hardware.SensorEvent;
22 import android.hardware.SensorEventListener;
23 import android.hardware.SensorManager;
24 import android.os.Bundle;
25 
26 import com.googlecode.android_scripting.jsonrpc.RpcReceiver;
27 import com.googlecode.android_scripting.rpc.Rpc;
28 import com.googlecode.android_scripting.rpc.RpcDefault;
29 import com.googlecode.android_scripting.rpc.RpcDeprecated;
30 import com.googlecode.android_scripting.rpc.RpcParameter;
31 import com.googlecode.android_scripting.rpc.RpcStartEvent;
32 import com.googlecode.android_scripting.rpc.RpcStopEvent;
33 
34 import java.util.Arrays;
35 import java.util.List;
36 
37 /**
38  * Exposes the SensorManager related functionality. <br>
39  * <br>
40  * <b>Guidance notes</b> <br>
41  * For reasons of economy the sensors on smart phones are usually low cost and, therefore, low
42  * accuracy (usually represented by 10 bit data). The floating point data values obtained from
43  * sensor readings have up to 16 decimal places, the majority of which are noise. On many phones the
44  * accelerometer is limited (by the phone manufacturer) to a maximum reading of 2g. The magnetometer
45  * (which also provides orientation readings) is strongly affected by the presence of ferrous metals
46  * and can give large errors in vehicles, on board ship etc.
47  *
48  * Following a startSensingTimed(A,B) api call sensor events are entered into the Event Queue (see
49  * EventFacade). For the A parameter: 1 = All Sensors, 2 = Accelerometer, 3 = Magnetometer and 4 =
50  * Light. The B parameter is the minimum delay between recordings in milliseconds. To avoid
51  * duplicate readings the minimum delay should be 20 milliseconds. The light sensor will probably be
52  * much slower (taking about 1 second to register a change in light level). Note that if the light
53  * level is constant no sensor events will be registered by the light sensor.
54  *
55  * Following a startSensingThreshold(A,B,C) api call sensor events greater than a given threshold
56  * are entered into the Event Queue. For the A parameter: 1 = Orientation, 2 = Accelerometer, 3 =
57  * Magnetometer and 4 = Light. The B parameter is the integer value of the required threshold level.
58  * For orientation sensing the integer threshold value is in milliradians. Since orientation events
59  * can exceed the threshold value for long periods only crossing and return events are recorded. The
60  * C parameter is the required axis (XYZ) of the sensor: 0 = No axis, 1 = X, 2 = Y, 3 = X+Y, 4 = Z,
61  * 5= X+Z, 6 = Y+Z, 7 = X+Y+Z. For orientation X = azimuth, Y = pitch and Z = roll. <br>
62  *
63  * <br>
64  * <b>Example (python)</b>
65  *
66  * <pre>
67  * import android, time
68  * droid = android.Android()
69  * droid.startSensingTimed(1, 250)
70  * time.sleep(1)
71  * s1 = droid.readSensors().result
72  * s2 = droid.sensorsGetAccuracy().result
73  * s3 = droid.sensorsGetLight().result
74  * s4 = droid.sensorsReadAccelerometer().result
75  * s5 = droid.sensorsReadMagnetometer().result
76  * s6 = droid.sensorsReadOrientation().result
77  * droid.stopSensing()
78  * </pre>
79  *
80  * Returns:<br>
81  * s1 = {u'accuracy': 3, u'pitch': -0.47323511242866517, u'xmag': 1.75, u'azimuth':
82  * -0.26701245009899138, u'zforce': 8.4718560000000007, u'yforce': 4.2495484000000001, u'time':
83  * 1297160391.2820001, u'ymag': -8.9375, u'zmag': -41.0625, u'roll': -0.031366908922791481,
84  * u'xforce': 0.23154590999999999}<br>
85  * s2 = 3 (Highest accuracy)<br>
86  * s3 = None ---(not available on many phones)<br>
87  * s4 = [0.23154590999999999, 4.2495484000000001, 8.4718560000000007] ----(x, y, z accelerations)<br>
88  * s5 = [1.75, -8.9375, -41.0625] -----(x, y, z magnetic readings)<br>
89  * s6 = [-0.26701245009899138, -0.47323511242866517, -0.031366908922791481] ---(azimuth, pitch, roll
90  * in radians)<br>
91  *
92  * @author Damon Kohler (damonkohler@gmail.com)
93  * @author Felix Arends (felix.arends@gmail.com)
94  * @author Alexey Reznichenko (alexey.reznichenko@gmail.com)
95  * @author Robbie Mathews (rjmatthews62@gmail.com)
96  * @author John Karwatzki (jokar49@gmail.com)
97  */
98 public class SensorManagerFacade extends RpcReceiver {
99   private final EventFacade mEventFacade;
100   private final SensorManager mSensorManager;
101 
102   private volatile Bundle mSensorReadings;
103 
104   private volatile Integer mAccuracy;
105   private volatile Integer mSensorNumber;
106   private volatile Integer mXAxis = 0;
107   private volatile Integer mYAxis = 0;
108   private volatile Integer mZAxis = 0;
109   private volatile Integer mThreshing = 0;
110   private volatile Integer mThreshOrientation = 0;
111   private volatile Integer mXCrossed = 0;
112   private volatile Integer mYCrossed = 0;
113   private volatile Integer mZCrossed = 0;
114 
115   private volatile Float mThreshold;
116   private volatile Float mXForce;
117   private volatile Float mYForce;
118   private volatile Float mZForce;
119 
120   private volatile Float mXMag;
121   private volatile Float mYMag;
122   private volatile Float mZMag;
123 
124   private volatile Float mLight;
125 
126   private volatile Double mAzimuth;
127   private volatile Double mPitch;
128   private volatile Double mRoll;
129 
130   private volatile Long mLastTime;
131   private volatile Long mDelayTime;
132 
133   private SensorEventListener mSensorListener;
134 
SensorManagerFacade(FacadeManager manager)135   public SensorManagerFacade(FacadeManager manager) {
136     super(manager);
137     mEventFacade = manager.getReceiver(EventFacade.class);
138     mSensorManager = (SensorManager) manager.getService().getSystemService(Context.SENSOR_SERVICE);
139   }
140 
141   @Rpc(description = "Starts recording sensor data to be available for polling.")
142   @RpcStartEvent("sensors")
startSensingTimed( @pcParametername = "sensorNumber", description = "1 = All, 2 = Accelerometer, 3 = Magnetometer and 4 = Light") Integer sensorNumber, @RpcParameter(name = "delayTime", description = "Minimum time between readings in milliseconds") Integer delayTime)143   public void startSensingTimed(
144       @RpcParameter(name = "sensorNumber", description = "1 = All, 2 = Accelerometer, 3 = Magnetometer and 4 = Light") Integer sensorNumber,
145       @RpcParameter(name = "delayTime", description = "Minimum time between readings in milliseconds") Integer delayTime) {
146     mSensorNumber = sensorNumber;
147     if (delayTime < 20) {
148       delayTime = 20;
149     }
150     mDelayTime = (long) (delayTime);
151     mLastTime = System.currentTimeMillis();
152     if (mSensorListener == null) {
153       mSensorListener = new SensorValuesCollector();
154       mSensorReadings = new Bundle();
155       switch (mSensorNumber) {
156       case 1:
157         for (Sensor sensor : mSensorManager.getSensorList(Sensor.TYPE_ALL)) {
158           mSensorManager.registerListener(mSensorListener, sensor,
159               SensorManager.SENSOR_DELAY_FASTEST);
160         }
161         break;
162       case 2:
163         for (Sensor sensor : mSensorManager.getSensorList(Sensor.TYPE_ACCELEROMETER)) {
164           mSensorManager.registerListener(mSensorListener, sensor,
165               SensorManager.SENSOR_DELAY_FASTEST);
166         }
167         break;
168       case 3:
169         for (Sensor sensor : mSensorManager.getSensorList(Sensor.TYPE_MAGNETIC_FIELD)) {
170           mSensorManager.registerListener(mSensorListener, sensor,
171               SensorManager.SENSOR_DELAY_FASTEST);
172         }
173         break;
174       case 4:
175         for (Sensor sensor : mSensorManager.getSensorList(Sensor.TYPE_LIGHT)) {
176           mSensorManager.registerListener(mSensorListener, sensor,
177               SensorManager.SENSOR_DELAY_FASTEST);
178         }
179       }
180     }
181   }
182 
183   @Rpc(description = "Records to the Event Queue sensor data exceeding a chosen threshold.")
184   @RpcStartEvent("threshold")
startSensingThreshold( @pcParametername = "sensorNumber", description = "1 = Orientation, 2 = Accelerometer, 3 = Magnetometer and 4 = Light") Integer sensorNumber, @RpcParameter(name = "threshold", description = "Threshold level for chosen sensor (integer)") Integer threshold, @RpcParameter(name = "axis", description = "0 = No axis, 1 = X, 2 = Y, 3 = X+Y, 4 = Z, 5= X+Z, 6 = Y+Z, 7 = X+Y+Z") Integer axis)185   public void startSensingThreshold(
186 
187       @RpcParameter(name = "sensorNumber", description = "1 = Orientation, 2 = Accelerometer, 3 = Magnetometer and 4 = Light") Integer sensorNumber,
188       @RpcParameter(name = "threshold", description = "Threshold level for chosen sensor (integer)") Integer threshold,
189       @RpcParameter(name = "axis", description = "0 = No axis, 1 = X, 2 = Y, 3 = X+Y, 4 = Z, 5= X+Z, 6 = Y+Z, 7 = X+Y+Z") Integer axis) {
190     mSensorNumber = sensorNumber;
191     mXAxis = axis & 1;
192     mYAxis = axis & 2;
193     mZAxis = axis & 4;
194     if (mSensorNumber == 1) {
195       mThreshing = 0;
196       mThreshOrientation = 1;
197       mThreshold = ((float) threshold) / ((float) 1000);
198     } else {
199       mThreshing = 1;
200       mThreshold = (float) threshold;
201     }
202     startSensingTimed(mSensorNumber, 20);
203   }
204 
205   @Rpc(description = "Returns the most recently recorded sensor data.")
readSensors()206   public Bundle readSensors() {
207     if (mSensorReadings == null) {
208       return null;
209     }
210     synchronized (mSensorReadings) {
211       return new Bundle(mSensorReadings);
212     }
213   }
214 
215   @Rpc(description = "Stops collecting sensor data.")
216   @RpcStopEvent("sensors")
stopSensing()217   public void stopSensing() {
218     mSensorManager.unregisterListener(mSensorListener);
219     mSensorListener = null;
220     mSensorReadings = null;
221     mThreshing = 0;
222     mThreshOrientation = 0;
223   }
224 
225   @Rpc(description = "Returns the most recently received accuracy value.")
sensorsGetAccuracy()226   public Integer sensorsGetAccuracy() {
227     return mAccuracy;
228   }
229 
230   @Rpc(description = "Returns the most recently received light value.")
sensorsGetLight()231   public Float sensorsGetLight() {
232     return mLight;
233   }
234 
235   @Rpc(description = "Returns the most recently received accelerometer values.", returns = "a List of Floats [(acceleration on the) X axis, Y axis, Z axis].")
sensorsReadAccelerometer()236   public List<Float> sensorsReadAccelerometer() {
237     synchronized (mSensorReadings) {
238       return Arrays.asList(mXForce, mYForce, mZForce);
239     }
240   }
241 
242   @Rpc(description = "Returns the most recently received magnetic field values.", returns = "a List of Floats [(magnetic field value for) X axis, Y axis, Z axis].")
sensorsReadMagnetometer()243   public List<Float> sensorsReadMagnetometer() {
244     synchronized (mSensorReadings) {
245       return Arrays.asList(mXMag, mYMag, mZMag);
246     }
247   }
248 
249   @Rpc(description = "Returns the most recently received orientation values.", returns = "a List of Doubles [azimuth, pitch, roll].")
sensorsReadOrientation()250   public List<Double> sensorsReadOrientation() {
251     synchronized (mSensorReadings) {
252       return Arrays.asList(mAzimuth, mPitch, mRoll);
253     }
254   }
255 
256   @Rpc(description = "Starts recording sensor data to be available for polling.")
257   @RpcDeprecated(value = "startSensingTimed or startSensingThreshhold", release = "4")
startSensing( @pcParametername = "sampleSize", description = "number of samples for calculating average readings") @pcDefault"5") Integer sampleSize)258   public void startSensing(
259       @RpcParameter(name = "sampleSize", description = "number of samples for calculating average readings") @RpcDefault("5") Integer sampleSize) {
260     if (mSensorListener == null) {
261       startSensingTimed(1, 220);
262     }
263   }
264 
265   @Override
shutdown()266   public void shutdown() {
267     stopSensing();
268   }
269 
270   private class SensorValuesCollector implements SensorEventListener {
271     private final static int MATRIX_SIZE = 9;
272 
273     private final RollingAverage mmAzimuth;
274     private final RollingAverage mmPitch;
275     private final RollingAverage mmRoll;
276 
277     private float[] mmGeomagneticValues;
278     private float[] mmGravityValues;
279     private float[] mmR;
280     private float[] mmOrientation;
281 
SensorValuesCollector()282     public SensorValuesCollector() {
283       mmAzimuth = new RollingAverage();
284       mmPitch = new RollingAverage();
285       mmRoll = new RollingAverage();
286     }
287 
postEvent()288     private void postEvent() {
289       mSensorReadings.putDouble("time", System.currentTimeMillis() / 1000.0);
290       mEventFacade.postEvent("sensors", mSensorReadings.clone());
291     }
292 
293     @Override
onAccuracyChanged(Sensor sensor, int accuracy)294     public void onAccuracyChanged(Sensor sensor, int accuracy) {
295       if (mSensorReadings == null) {
296         return;
297       }
298       synchronized (mSensorReadings) {
299         mSensorReadings.putInt("accuracy", accuracy);
300         mAccuracy = accuracy;
301 
302       }
303     }
304 
305     @Override
onSensorChanged(SensorEvent event)306     public void onSensorChanged(SensorEvent event) {
307       if (mSensorReadings == null) {
308         return;
309       }
310       synchronized (mSensorReadings) {
311         switch (event.sensor.getType()) {
312         case Sensor.TYPE_ACCELEROMETER:
313           mXForce = event.values[0];
314           mYForce = event.values[1];
315           mZForce = event.values[2];
316           if (mThreshing == 0) {
317             mSensorReadings.putFloat("xforce", mXForce);
318             mSensorReadings.putFloat("yforce", mYForce);
319             mSensorReadings.putFloat("zforce", mZForce);
320             if ((mSensorNumber == 2) && (System.currentTimeMillis() > (mDelayTime + mLastTime))) {
321               mLastTime = System.currentTimeMillis();
322               postEvent();
323             }
324           }
325           if ((mThreshing == 1) && (mSensorNumber == 2)) {
326             if ((Math.abs(mXForce) > mThreshold) && (mXAxis == 1)) {
327               mSensorReadings.putFloat("xforce", mXForce);
328               postEvent();
329             }
330 
331             if ((Math.abs(mYForce) > mThreshold) && (mYAxis == 2)) {
332               mSensorReadings.putFloat("yforce", mYForce);
333               postEvent();
334             }
335 
336             if ((Math.abs(mZForce) > mThreshold) && (mZAxis == 4)) {
337               mSensorReadings.putFloat("zforce", mZForce);
338               postEvent();
339             }
340           }
341 
342           mmGravityValues = event.values.clone();
343           break;
344         case Sensor.TYPE_MAGNETIC_FIELD:
345           mXMag = event.values[0];
346           mYMag = event.values[1];
347           mZMag = event.values[2];
348           if (mThreshing == 0) {
349             mSensorReadings.putFloat("xMag", mXMag);
350             mSensorReadings.putFloat("yMag", mYMag);
351             mSensorReadings.putFloat("zMag", mZMag);
352             if ((mSensorNumber == 3) && (System.currentTimeMillis() > (mDelayTime + mLastTime))) {
353               mLastTime = System.currentTimeMillis();
354               postEvent();
355             }
356           }
357           if ((mThreshing == 1) && (mSensorNumber == 3)) {
358             if ((Math.abs(mXMag) > mThreshold) && (mXAxis == 1)) {
359               mSensorReadings.putFloat("xforce", mXMag);
360               postEvent();
361             }
362             if ((Math.abs(mYMag) > mThreshold) && (mYAxis == 2)) {
363               mSensorReadings.putFloat("yforce", mYMag);
364               postEvent();
365             }
366             if ((Math.abs(mZMag) > mThreshold) && (mZAxis == 4)) {
367               mSensorReadings.putFloat("zforce", mZMag);
368               postEvent();
369             }
370           }
371           mmGeomagneticValues = event.values.clone();
372           break;
373         case Sensor.TYPE_LIGHT:
374           mLight = event.values[0];
375           if (mThreshing == 0) {
376             mSensorReadings.putFloat("light", mLight);
377             if ((mSensorNumber == 4) && (System.currentTimeMillis() > (mDelayTime + mLastTime))) {
378               mLastTime = System.currentTimeMillis();
379               postEvent();
380             }
381           }
382           if ((mThreshing == 1) && (mSensorNumber == 4)) {
383             if (mLight > mThreshold) {
384               mSensorReadings.putFloat("light", mLight);
385               postEvent();
386             }
387           }
388           break;
389 
390         }
391         if (mSensorNumber == 1) {
392           if (mmGeomagneticValues != null && mmGravityValues != null) {
393             if (mmR == null) {
394               mmR = new float[MATRIX_SIZE];
395             }
396             if (SensorManager.getRotationMatrix(mmR, null, mmGravityValues, mmGeomagneticValues)) {
397               if (mmOrientation == null) {
398                 mmOrientation = new float[3];
399               }
400               SensorManager.getOrientation(mmR, mmOrientation);
401               mmAzimuth.add(mmOrientation[0]);
402               mmPitch.add(mmOrientation[1]);
403               mmRoll.add(mmOrientation[2]);
404 
405               mAzimuth = mmAzimuth.get();
406               mPitch = mmPitch.get();
407               mRoll = mmRoll.get();
408               if (mThreshOrientation == 0) {
409                 mSensorReadings.putDouble("azimuth", mAzimuth);
410                 mSensorReadings.putDouble("pitch", mPitch);
411                 mSensorReadings.putDouble("roll", mRoll);
412                 if ((mSensorNumber == 1) && (System.currentTimeMillis() > (mDelayTime + mLastTime))) {
413                   mLastTime = System.currentTimeMillis();
414                   postEvent();
415                 }
416               }
417               if ((mThreshOrientation == 1) && (mSensorNumber == 1)) {
418                 if ((mXAxis == 1) && (mXCrossed == 0)) {
419                   if (Math.abs(mAzimuth) > ((double) mThreshold)) {
420                     mSensorReadings.putDouble("azimuth", mAzimuth);
421                     postEvent();
422                     mXCrossed = 1;
423                   }
424                 }
425                 if ((mXAxis == 1) && (mXCrossed == 1)) {
426                   if (Math.abs(mAzimuth) < ((double) mThreshold)) {
427                     mSensorReadings.putDouble("azimuth", mAzimuth);
428                     postEvent();
429                     mXCrossed = 0;
430                   }
431                 }
432                 if ((mYAxis == 2) && (mYCrossed == 0)) {
433                   if (Math.abs(mPitch) > ((double) mThreshold)) {
434                     mSensorReadings.putDouble("pitch", mPitch);
435                     postEvent();
436                     mYCrossed = 1;
437                   }
438                 }
439                 if ((mYAxis == 2) && (mYCrossed == 1)) {
440                   if (Math.abs(mPitch) < ((double) mThreshold)) {
441                     mSensorReadings.putDouble("pitch", mPitch);
442                     postEvent();
443                     mYCrossed = 0;
444                   }
445                 }
446                 if ((mZAxis == 4) && (mZCrossed == 0)) {
447                   if (Math.abs(mRoll) > ((double) mThreshold)) {
448                     mSensorReadings.putDouble("roll", mRoll);
449                     postEvent();
450                     mZCrossed = 1;
451                   }
452                 }
453                 if ((mZAxis == 4) && (mZCrossed == 1)) {
454                   if (Math.abs(mRoll) < ((double) mThreshold)) {
455                     mSensorReadings.putDouble("roll", mRoll);
456                     postEvent();
457                     mZCrossed = 0;
458                   }
459                 }
460               }
461             }
462           }
463         }
464       }
465     }
466   }
467 
468   static class RollingAverage {
469     private final int mmSampleSize;
470     private final double mmData[];
471     private int mmIndex = 0;
472     private boolean mmFilled = false;
473     private double mmSum = 0.0;
474 
RollingAverage()475     public RollingAverage() {
476       mmSampleSize = 5;
477       mmData = new double[mmSampleSize];
478     }
479 
add(double value)480     public void add(double value) {
481       mmSum -= mmData[mmIndex];
482       mmData[mmIndex] = value;
483       mmSum += mmData[mmIndex];
484       ++mmIndex;
485       mmIndex %= mmSampleSize;
486       mmFilled = (!mmFilled) ? mmIndex == 0 : mmFilled;
487     }
488 
get()489     public double get() throws IllegalStateException {
490       if (!mmFilled && mmIndex == 0) {
491         throw new IllegalStateException("No values to average.");
492       }
493       return (mmFilled) ? (mmSum / mmSampleSize) : (mmSum / mmIndex);
494     }
495   }
496 }
497