AI2 Component  (Version nb184)
AccelerometerSensor.java
Go to the documentation of this file.
1 // -*- mode: java; c-basic-offset: 2; -*-
2 // Copyright 2009-2011 Google, All Rights reserved
3 // Copyright 2011-2012 MIT, All rights reserved
4 // Released under the Apache License, Version 2.0
5 // http://www.apache.org/licenses/LICENSE-2.0
6 
7 
8 package com.google.appinventor.components.runtime;
9 
10 import android.content.Context;
11 import android.content.res.Configuration;
12 import android.content.res.Resources;
13 import android.hardware.Sensor;
14 import android.hardware.SensorEvent;
15 import android.hardware.SensorEventListener;
16 import android.hardware.SensorManager;
17 import android.os.Build;
18 import android.os.Handler;
19 import android.util.Log;
20 import android.view.Surface;
21 import android.view.WindowManager;
33 
34 import java.util.LinkedList;
35 import java.util.List;
36 import java.util.Queue;
37 
69 // TODO(user): ideas - event for knocking
71  description = "Non-visible component that can detect shaking and " +
72  "measure acceleration approximately in three dimensions using SI units " +
73  "(m/s<sup>2</sup>). The components are: <ul>\n" +
74  "<li> <strong>xAccel</strong>: 0 when the phone is at rest on a flat " +
75  " surface, positive when the phone is tilted to the right (i.e., " +
76  " its left side is raised), and negative when the phone is tilted " +
77  " to the left (i.e., its right size is raised).</li>\n " +
78  "<li> <strong>yAccel</strong>: 0 when the phone is at rest on a flat " +
79  " surface, positive when its bottom is raised, and negative when " +
80  " its top is raised. </li>\n " +
81  "<li> <strong>zAccel</strong>: Equal to -9.8 (earth's gravity in meters per " +
82  " second per second when the device is at rest parallel to the ground " +
83  " with the display facing up, " +
84  " 0 when perpendicular to the ground, and +9.8 when facing down. " +
85  " The value can also be affected by accelerating it with or against " +
86  " gravity. </li></ul>",
87  category = ComponentCategory.SENSORS,
88  nonVisible = true,
89  iconName = "images/accelerometersensor.png")
92  implements OnPauseListener, OnResumeListener, SensorComponent, SensorEventListener, Deleteable {
93 
94  // Logging and Debugging
95  private final static String LOG_TAG = "AccelerometerSensor";
96  private final static boolean DEBUG = true;
97 
98  // Shake thresholds - derived by trial
99  private static final double weakShakeThreshold = 5.0;
100  private static final double moderateShakeThreshold = 13.0;
101  private static final double strongShakeThreshold = 20.0;
102 
103  // Cache for shake detection
104  private static final int SENSOR_CACHE_SIZE = 10;
105  private final Queue<Float> X_CACHE = new LinkedList<Float>();
106  private final Queue<Float> Y_CACHE = new LinkedList<Float>();
107  private final Queue<Float> Z_CACHE = new LinkedList<Float>();
108 
109  // Backing for sensor values
110  private float xAccel;
111  private float yAccel;
112  private float zAccel;
113 
114  private int accuracy;
115  private int sensitivity;
116  private volatile int deviceDefaultOrientation;
117 
118  private final SensorManager sensorManager;
119 
120  private final WindowManager windowManager;
121  private final Resources resources;
122 
123  // Indicates whether the accelerometer should generate events
124  private boolean enabled;
125 
126  //Specifies the minimum time interval between calls to Shaking()
127  private int minimumInterval;
128 
129  //Specifies the time when Shaking() was last called
130  private long timeLastShook;
131 
132  private Sensor accelerometerSensor;
133 
134  // Set to true to disable landscape mode tablet fix
135  private boolean legacyMode = false;
136 
137  // Used to launch Runnables on the UI Thread after a delay
138  private final Handler androidUIHandler;
139 
146  super(container.$form());
148  form.registerForOnPause(this);
149 
150  enabled = true;
151  resources = container.$context().getResources();
152  windowManager = (WindowManager) container.$context().getSystemService(Context.WINDOW_SERVICE);
153  sensorManager = (SensorManager) container.$context().getSystemService(Context.SENSOR_SERVICE);
154  accelerometerSensor = sensorManager.getDefaultSensor(Sensor.TYPE_ACCELEROMETER);
155  androidUIHandler = new Handler();
156  startListening();
157  MinimumInterval(400);
159  }
160 
161 
170  category = PropertyCategory.BEHAVIOR,
171  description = "The minimum interval, in milliseconds, between phone shakes")
172  public int MinimumInterval() {
173  return minimumInterval;
174  }
175 
184  defaultValue = "400") //Default value derived by trial of 12 people on 3 different devices
186  public void MinimumInterval(int interval) {
187  minimumInterval = interval;
188  }
189 
199  category = PropertyCategory.APPEARANCE,
200  description = "A number that encodes how sensitive the accelerometer is. " +
201  "The choices are: 1 = weak, 2 = moderate, " +
202  " 3 = strong.")
203  public int Sensitivity() {
204  return sensitivity;
205  }
206 
217  defaultValue = Component.ACCELEROMETER_SENSITIVITY_MODERATE + "")
219  public void Sensitivity(int sensitivity) {
220  if ((sensitivity == 1) || (sensitivity == 2) || (sensitivity == 3)) {
221  this.sensitivity = sensitivity;
222  } else {
223  form.dispatchErrorOccurredEvent(this, "Sensitivity",
225  }
226  }
227 
231  @SimpleEvent
232  public void AccelerationChanged(float xAccel, float yAccel, float zAccel) {
233  this.xAccel = xAccel;
234  this.yAccel = yAccel;
235  this.zAccel = zAccel;
236 
237  addToSensorCache(X_CACHE, xAccel);
238  addToSensorCache(Y_CACHE, yAccel);
239  addToSensorCache(Z_CACHE, zAccel);
240 
241  long currentTime = System.currentTimeMillis();
242 
243  //Checks whether the phone is shaking and the minimum interval
244  //has elapsed since the last registered a shaking event.
245  if ((isShaking(X_CACHE, xAccel) || isShaking(Y_CACHE, yAccel) || isShaking(Z_CACHE, zAccel))
246  && (timeLastShook == 0 || currentTime >= timeLastShook + minimumInterval)){
247  timeLastShook = currentTime;
248  Shaking();
249  }
250 
251  EventDispatcher.dispatchEvent(this, "AccelerationChanged", xAccel, yAccel, zAccel);
252  }
253 
255  if (Build.VERSION.SDK_INT < SdkLevel.LEVEL_FROYO) {
256  // getRotation() is unavailable on versions of Android lower tha Froyo, so assume a default
257  // orientation of PORTRAIT (which was the implied assumption before we added this check).
258  return Configuration.ORIENTATION_PORTRAIT;
259  }
260  Configuration config = resources.getConfiguration();
261  int rotation = windowManager.getDefaultDisplay().getRotation();
262  if (DEBUG) {
263  Log.d(LOG_TAG, "rotation = " + rotation);
264  Log.d(LOG_TAG, "config.orientation = " + config.orientation);
265  }
266  // return config.orientation;
267 
268  if ( ((rotation == Surface.ROTATION_0 || rotation == Surface.ROTATION_180) &&
269  config.orientation == Configuration.ORIENTATION_LANDSCAPE)
270  || ((rotation == Surface.ROTATION_90 || rotation == Surface.ROTATION_270) &&
271  config.orientation == Configuration.ORIENTATION_PORTRAIT)) {
272  return Configuration.ORIENTATION_LANDSCAPE;
273  } else {
274  return Configuration.ORIENTATION_PORTRAIT;
275  }
276 }
277 
281  @SimpleEvent
282  public void Shaking() {
283  EventDispatcher.dispatchEvent(this, "Shaking");
284  }
285 
293  description = "Returns whether the accelerometer is available on the device.",
294  category = PropertyCategory.BEHAVIOR)
295  public boolean Available() {
296  List<Sensor> sensors = sensorManager.getSensorList(Sensor.TYPE_ACCELEROMETER);
297  return (sensors.size() > 0);
298  }
299 
308  category = PropertyCategory.BEHAVIOR)
309  public boolean Enabled() {
310  return enabled;
311  }
312 
313  // Assumes that sensorManager has been initialized, which happens in constructor
314  private void startListening() {
315  // save the device default orientation (portrait or landscape)
316  androidUIHandler.postDelayed(new Runnable() {
317  @Override
318  public void run() {
319  AccelerometerSensor.this.deviceDefaultOrientation = getDeviceDefaultOrientation();
320  if (DEBUG) {
321  Log.d(LOG_TAG, "deviceDefaultOrientation = " + AccelerometerSensor.this.deviceDefaultOrientation);
322  Log.d(LOG_TAG, "Configuration.ORIENTATION_LANDSCAPE = " + Configuration.ORIENTATION_LANDSCAPE);
323  Log.d(LOG_TAG, "Configuration.ORIENTATION_PORTRAIT = " + Configuration.ORIENTATION_PORTRAIT);
324  }
325  }
326  }, 32); // Wait 32ms for the UI to settle down
327 
328  sensorManager.registerListener(this, accelerometerSensor, SensorManager.SENSOR_DELAY_GAME);
329  }
330 
331  // Assumes that sensorManager has been initialized, which happens in constructor
332  private void stopListening() {
333  sensorManager.unregisterListener(this);
334  }
335 
344  @DesignerProperty(editorType = PropertyTypeConstants.PROPERTY_TYPE_BOOLEAN,
345  defaultValue = "True")
346  @SimpleProperty
347  public void Enabled(boolean enabled) {
348  if (this.enabled == enabled) {
349  return;
350  }
351  this.enabled = enabled;
352  if (enabled) {
353  startListening();
354  } else {
355  stopListening();
356  }
357  }
358 
366  category = PropertyCategory.BEHAVIOR)
367  public float XAccel() {
368  return xAccel;
369  }
370 
378  category = PropertyCategory.BEHAVIOR)
379  public float YAccel() {
380  return yAccel;
381  }
382 
390  category = PropertyCategory.BEHAVIOR)
391  public float ZAccel() {
392  return zAccel;
393  }
394 
395  /*
396  * Updating sensor cache, replacing oldest values.
397  */
398  private void addToSensorCache(Queue<Float> cache, float value) {
399  if (cache.size() >= SENSOR_CACHE_SIZE) {
400  cache.remove();
401  }
402  cache.add(value);
403  }
404 
405  /*
406  * Indicates whether there was a sudden, unusual movement.
407  */
408  // TODO(user): Maybe this can be improved.
409  // See http://www.utdallas.edu/~rxb023100/pubs/Accelerometer_WBSN.pdf.
410  private boolean isShaking(Queue<Float> cache, float currentValue) {
411  float average = 0;
412  for (float value : cache) {
413  average += value;
414  }
415 
416  average /= cache.size();
417 
418  if (Sensitivity() == 1) { //sensitivity is weak
419  return Math.abs(average - currentValue) > strongShakeThreshold;
420  } else if (Sensitivity() == 2) { //sensitivity is moderate
421  return ((Math.abs(average - currentValue) > moderateShakeThreshold)
422  && (Math.abs(average - currentValue) < strongShakeThreshold));
423  } else { //sensitivity is strong
424  return ((Math.abs(average - currentValue) > weakShakeThreshold)
425  && (Math.abs(average - currentValue) < moderateShakeThreshold));
426  }
427  }
428 
429  @DesignerProperty(editorType = PropertyTypeConstants.PROPERTY_TYPE_BOOLEAN,
430  defaultValue = "False")
431  @SimpleProperty(userVisible = false,
432  description="Prior to the release that added this property the AccelerometerSensor " +
433  "component passed through sensor values directly as received from the " +
434  "Android system. However these values do not compensate for tablets " +
435  "that default to Landscape mode, requiring the MIT App Inventor " +
436  "programmer to compensate. However compensating would result in " +
437  "incorrect results in Portrait mode devices such as phones. " +
438  "We now detect Landscape mode tablets and perform the compensation. " +
439  "However if your project is already compensating for the change, you " +
440  "will now get incorrect results. Although our preferred solution is for " +
441  "you to update your project, you can also just set this property to “true” " +
442  "and our compensation code will be deactivated. Note: We recommend that " +
443  "you update your project as we may remove this property in a future " +
444  "release.")
445  public void LegacyMode(boolean legacyMode) {
446  this.legacyMode = legacyMode;
447  }
448 
449  public boolean LegacyMode() {
450  return legacyMode;
451  }
452 
453  // SensorListener implementation
454  @Override
455  public void onSensorChanged(SensorEvent sensorEvent) {
456  if (enabled) {
457  final float[] values = sensorEvent.values;
458  // make landscapePrimary devices report acceleration as if they were
459  // portraitPrimary
460  if ((deviceDefaultOrientation == Configuration.ORIENTATION_LANDSCAPE) &&
461  !legacyMode) {
462  xAccel = values[1];
463  yAccel = -values[0];
464  } else {
465  xAccel = values[0];
466  yAccel = values[1];
467  }
468  zAccel = values[2];
469  accuracy = sensorEvent.accuracy;
470  AccelerationChanged(xAccel, yAccel, zAccel);
471  }
472  }
473 
474  @Override
475  public void onAccuracyChanged(Sensor sensor, int accuracy) {
476  // TODO(markf): Figure out if we actually need to do something here.
477  }
478 
479  // OnResumeListener implementation
480 
481  @Override
482  public void onResume() {
483  if (enabled) {
484  startListening();
485  }
486  }
487 
488  // OnPauseListener implementation
489 
490  @Override
491  public void onPause() {
492  if (enabled) {
493  stopListening();
494  }
495  }
496 
497  // Deleteable implementation
498 
499  @Override
500  public void onDelete() {
501  if (enabled) {
502  stopListening();
503  }
504  }
505 }
com.google.appinventor.components.runtime.EventDispatcher
Definition: EventDispatcher.java:22
com.google.appinventor.components.runtime.AccelerometerSensor.onAccuracyChanged
void onAccuracyChanged(Sensor sensor, int accuracy)
Definition: AccelerometerSensor.java:475
com.google.appinventor.components.runtime.AccelerometerSensor.MinimumInterval
int MinimumInterval()
Definition: AccelerometerSensor.java:172
com.google.appinventor.components.runtime.util.ErrorMessages
Definition: ErrorMessages.java:17
com.google.appinventor.components.runtime.util
-*- mode: java; c-basic-offset: 2; -*-
Definition: AccountChooser.java:7
com.google.appinventor.components.common.YaVersion
Definition: YaVersion.java:14
com.google.appinventor.components.annotations.DesignerProperty
Definition: DesignerProperty.java:25
com.google.appinventor.components.runtime.Form.registerForOnResume
void registerForOnResume(OnResumeListener component)
Definition: Form.java:740
com.google.appinventor.components.runtime.AccelerometerSensor.LegacyMode
boolean LegacyMode()
Definition: AccelerometerSensor.java:449
com.google.appinventor.components
com.google.appinventor.components.common.PropertyTypeConstants.PROPERTY_TYPE_NON_NEGATIVE_INTEGER
static final String PROPERTY_TYPE_NON_NEGATIVE_INTEGER
Definition: PropertyTypeConstants.java:206
com.google.appinventor.components.annotations.DesignerComponent
Definition: DesignerComponent.java:22
com.google.appinventor.components.annotations.SimpleEvent
Definition: SimpleEvent.java:20
com.google.appinventor.components.runtime.AccelerometerSensor.Sensitivity
void Sensitivity(int sensitivity)
Definition: AccelerometerSensor.java:219
com.google.appinventor.components.annotations.PropertyCategory.BEHAVIOR
BEHAVIOR
Definition: PropertyCategory.java:15
com.google.appinventor.components.runtime.AccelerometerSensor.XAccel
float XAccel()
Definition: AccelerometerSensor.java:367
com.google.appinventor.components.runtime.util.ErrorMessages.ERROR_BAD_VALUE_FOR_ACCELEROMETER_SENSITIVITY
static final int ERROR_BAD_VALUE_FOR_ACCELEROMETER_SENSITIVITY
Definition: ErrorMessages.java:170
com.google.appinventor.components.runtime.AccelerometerSensor.YAccel
float YAccel()
Definition: AccelerometerSensor.java:379
com.google.appinventor.components.runtime.AccelerometerSensor.Available
boolean Available()
Definition: AccelerometerSensor.java:295
com.google.appinventor.components.common.ComponentCategory.SENSORS
SENSORS
Definition: ComponentCategory.java:55
com.google.appinventor.components.runtime.AccelerometerSensor.ZAccel
float ZAccel()
Definition: AccelerometerSensor.java:391
com.google.appinventor.components.runtime.OnResumeListener
Definition: OnResumeListener.java:14
com.google.appinventor.components.runtime.AccelerometerSensor.onPause
void onPause()
Definition: AccelerometerSensor.java:491
com.google.appinventor.components.runtime.AccelerometerSensor.Sensitivity
int Sensitivity()
Definition: AccelerometerSensor.java:203
com.google.appinventor.components.runtime.AccelerometerSensor.AccelerometerSensor
AccelerometerSensor(ComponentContainer container)
Definition: AccelerometerSensor.java:145
com.google.appinventor.components.runtime.EventDispatcher.dispatchEvent
static boolean dispatchEvent(Component component, String eventName, Object...args)
Definition: EventDispatcher.java:188
com.google.appinventor.components.common.PropertyTypeConstants.PROPERTY_TYPE_ACCELEROMETER_SENSITIVITY
static final String PROPERTY_TYPE_ACCELEROMETER_SENSITIVITY
Definition: PropertyTypeConstants.java:50
com.google.appinventor.components.runtime.AccelerometerSensor.MinimumInterval
void MinimumInterval(int interval)
Definition: AccelerometerSensor.java:186
com.google.appinventor.components.runtime.AndroidNonvisibleComponent
Definition: AndroidNonvisibleComponent.java:17
com.google.appinventor.components.runtime.util.SdkLevel
Definition: SdkLevel.java:19
com.google.appinventor.components.runtime.AccelerometerSensor.AccelerationChanged
void AccelerationChanged(float xAccel, float yAccel, float zAccel)
Definition: AccelerometerSensor.java:232
com.google.appinventor.components.runtime.OnPauseListener
Definition: OnPauseListener.java:14
com.google.appinventor.components.runtime.AccelerometerSensor.getDeviceDefaultOrientation
int getDeviceDefaultOrientation()
Definition: AccelerometerSensor.java:254
com.google.appinventor.components.annotations.SimpleProperty
Definition: SimpleProperty.java:23
com.google.appinventor.components.annotations.PropertyCategory
Definition: PropertyCategory.java:13
com.google.appinventor.components.runtime.AccelerometerSensor.Shaking
void Shaking()
Definition: AccelerometerSensor.java:282
com.google.appinventor.components.runtime.ComponentContainer
Definition: ComponentContainer.java:16
com.google.appinventor.components.runtime
Copyright 2009-2011 Google, All Rights reserved.
Definition: AccelerometerSensor.java:8
com.google.appinventor.components.runtime.AccelerometerSensor.Enabled
boolean Enabled()
Definition: AccelerometerSensor.java:309
com.google.appinventor.components.runtime.Component
Definition: Component.java:17
com.google.appinventor.components.runtime.Deleteable
Definition: Deleteable.java:15
com.google.appinventor.components.common
Definition: ComponentCategory.java:7
com.google.appinventor.components.runtime.AccelerometerSensor.onResume
void onResume()
Definition: AccelerometerSensor.java:482
com.google.appinventor.components.common.ComponentCategory
Definition: ComponentCategory.java:48
com.google.appinventor.components.runtime.util.SdkLevel.LEVEL_FROYO
static final int LEVEL_FROYO
Definition: SdkLevel.java:25
com.google.appinventor.components.runtime.AccelerometerSensor.onSensorChanged
void onSensorChanged(SensorEvent sensorEvent)
Definition: AccelerometerSensor.java:455
com.google.appinventor.components.runtime.Form.dispatchErrorOccurredEvent
void dispatchErrorOccurredEvent(final Component component, final String functionName, final int errorNumber, final Object... messageArgs)
Definition: Form.java:1011
com.google.appinventor.components.runtime.Form.registerForOnPause
void registerForOnPause(OnPauseListener component)
Definition: Form.java:780
com.google.appinventor.components.annotations.SimpleObject
Definition: SimpleObject.java:23
com.google
com
com.google.appinventor.components.runtime.AccelerometerSensor.onDelete
void onDelete()
Definition: AccelerometerSensor.java:500
com.google.appinventor.components.runtime.AccelerometerSensor
Definition: AccelerometerSensor.java:91
com.google.appinventor.components.runtime.ComponentContainer.$form
Form $form()
com.google.appinventor.components.runtime.ComponentContainer.$context
Activity $context()
com.google.appinventor.components.common.YaVersion.ACCELEROMETERSENSOR_COMPONENT_VERSION
static final int ACCELEROMETERSENSOR_COMPONENT_VERSION
Definition: YaVersion.java:650
com.google.appinventor.components.runtime.Component.ACCELEROMETER_SENSITIVITY_MODERATE
static final int ACCELEROMETER_SENSITIVITY_MODERATE
Definition: Component.java:40
com.google.appinventor.components.runtime.AndroidNonvisibleComponent.form
final Form form
Definition: AndroidNonvisibleComponent.java:19
com.google.appinventor.components.annotations.PropertyCategory.APPEARANCE
APPEARANCE
Definition: PropertyCategory.java:16
com.google.appinventor.components.runtime.SensorComponent
Definition: SensorComponent.java:16
com.google.appinventor.components.common.PropertyTypeConstants
Definition: PropertyTypeConstants.java:14
com.google.appinventor.components.annotations
com.google.appinventor.components.runtime.AccelerometerSensor.Enabled
void Enabled(boolean enabled)
Definition: AccelerometerSensor.java:347
com.google.appinventor