8 package com.google.appinventor.components.runtime;
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;
34 import java.util.LinkedList;
35 import java.util.List;
36 import java.util.Queue;
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>",
89 iconName =
"images/accelerometersensor.png")
95 private final static String LOG_TAG =
"AccelerometerSensor";
96 private final static boolean DEBUG =
true;
99 private static final double weakShakeThreshold = 5.0;
100 private static final double moderateShakeThreshold = 13.0;
101 private static final double strongShakeThreshold = 20.0;
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>();
110 private float xAccel;
111 private float yAccel;
112 private float zAccel;
114 private int accuracy;
115 private int sensitivity;
116 private volatile int deviceDefaultOrientation;
118 private final SensorManager sensorManager;
120 private final WindowManager windowManager;
121 private final Resources resources;
124 private boolean enabled;
127 private int minimumInterval;
130 private long timeLastShook;
132 private Sensor accelerometerSensor;
135 private boolean legacyMode =
false;
138 private final Handler androidUIHandler;
146 super(container.
$form());
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();
171 description =
"The minimum interval, in milliseconds, between phone shakes")
173 return minimumInterval;
184 defaultValue =
"400")
187 minimumInterval = interval;
200 description =
"A number that encodes how sensitive the accelerometer is. " +
201 "The choices are: 1 = weak, 2 = moderate, " +
220 if ((sensitivity == 1) || (sensitivity == 2) || (sensitivity == 3)) {
221 this.sensitivity = sensitivity;
233 this.xAccel = xAccel;
234 this.yAccel = yAccel;
235 this.zAccel = zAccel;
237 addToSensorCache(X_CACHE, xAccel);
238 addToSensorCache(Y_CACHE, yAccel);
239 addToSensorCache(Z_CACHE, zAccel);
241 long currentTime = System.currentTimeMillis();
245 if ((isShaking(X_CACHE, xAccel) || isShaking(Y_CACHE, yAccel) || isShaking(Z_CACHE, zAccel))
246 && (timeLastShook == 0 || currentTime >= timeLastShook + minimumInterval)){
247 timeLastShook = currentTime;
258 return Configuration.ORIENTATION_PORTRAIT;
260 Configuration config = resources.getConfiguration();
261 int rotation = windowManager.getDefaultDisplay().getRotation();
263 Log.d(LOG_TAG,
"rotation = " + rotation);
264 Log.d(LOG_TAG,
"config.orientation = " + config.orientation);
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;
274 return Configuration.ORIENTATION_PORTRAIT;
293 description =
"Returns whether the accelerometer is available on the device.",
296 List<Sensor> sensors = sensorManager.getSensorList(Sensor.TYPE_ACCELEROMETER);
297 return (sensors.size() > 0);
314 private void startListening() {
316 androidUIHandler.postDelayed(
new Runnable() {
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);
328 sensorManager.registerListener(
this, accelerometerSensor, SensorManager.SENSOR_DELAY_GAME);
332 private void stopListening() {
333 sensorManager.unregisterListener(
this);
344 @DesignerProperty(editorType = PropertyTypeConstants.PROPERTY_TYPE_BOOLEAN,
345 defaultValue =
"True")
348 if (this.enabled == enabled) {
351 this.enabled = enabled;
398 private void addToSensorCache(Queue<Float> cache,
float value) {
399 if (cache.size() >= SENSOR_CACHE_SIZE) {
410 private boolean isShaking(Queue<Float> cache,
float currentValue) {
412 for (
float value : cache) {
416 average /= cache.size();
419 return Math.abs(average - currentValue) > strongShakeThreshold;
421 return ((Math.abs(average - currentValue) > moderateShakeThreshold)
422 && (Math.abs(average - currentValue) < strongShakeThreshold));
424 return ((Math.abs(average - currentValue) > weakShakeThreshold)
425 && (Math.abs(average - currentValue) < moderateShakeThreshold));
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 " +
446 this.legacyMode = legacyMode;
457 final float[] values = sensorEvent.values;
460 if ((deviceDefaultOrientation == Configuration.ORIENTATION_LANDSCAPE) &&
469 accuracy = sensorEvent.accuracy;