Merge "New orientation listener." into honeycomb

This commit is contained in:
Jeff Brown
2011-01-24 15:31:22 -08:00
committed by Android (Google) Code Review
5 changed files with 1059 additions and 378 deletions

View File

@@ -1679,6 +1679,16 @@ public final class Settings {
*/
public static final String POINTER_LOCATION = "pointer_location";
/**
* Log raw orientation data from {@link WindowOrientationListener} for use with the
* orientationplot.py tool.
* 0 = no
* 1 = yes
* @hide
*/
public static final String WINDOW_ORIENTATION_LISTENER_LOG =
"window_orientation_listener_log";
/**
* Whether to play a sound for low-battery alerts.
* @hide

View File

@@ -23,6 +23,7 @@ import android.hardware.SensorEventListener;
import android.hardware.SensorManager;
import android.util.Config;
import android.util.Log;
import android.util.Slog;
/**
* A special helper class used by the WindowManager
@@ -33,17 +34,27 @@ import android.util.Log;
* "App/Activity/Screen Orientation" to ensure that all orientation
* modes still work correctly.
*
* You can also visualize the behavior of the WindowOrientationListener by
* enabling the window orientation listener log using the Development Settings
* in the Dev Tools application (Development.apk)
* and running frameworks/base/tools/orientationplot/orientationplot.py.
*
* More information about how to tune this algorithm in
* frameworks/base/tools/orientationplot/README.txt.
*
* @hide
*/
public abstract class WindowOrientationListener {
private static final String TAG = "WindowOrientationListener";
private static final boolean DEBUG = false;
private static final boolean localLOGV = DEBUG || Config.DEBUG;
private SensorManager mSensorManager;
private boolean mEnabled = false;
private boolean mEnabled;
private int mRate;
private Sensor mSensor;
private SensorEventListenerImpl mSensorEventListener;
boolean mLogEnabled;
/**
* Creates a new WindowOrientationListener.
@@ -51,7 +62,7 @@ public abstract class WindowOrientationListener {
* @param context for the WindowOrientationListener.
*/
public WindowOrientationListener(Context context) {
this(context, SensorManager.SENSOR_DELAY_NORMAL);
this(context, SensorManager.SENSOR_DELAY_UI);
}
/**
@@ -63,9 +74,7 @@ public abstract class WindowOrientationListener {
* value of {@link android.hardware.SensorManager#SENSOR_DELAY_NORMAL
* SENSOR_DELAY_NORMAL} for simple screen orientation change detection.
*
* This constructor is private since no one uses it and making it public would complicate
* things, since the lowpass filtering code depends on the actual sampling period, and there's
* no way to get the period from SensorManager based on the rate constant.
* This constructor is private since no one uses it.
*/
private WindowOrientationListener(Context context, int rate) {
mSensorManager = (SensorManager)context.getSystemService(Context.SENSOR_SERVICE);
@@ -108,12 +117,11 @@ public abstract class WindowOrientationListener {
}
}
public void setAllow180Rotation(boolean allowed) {
if (mSensorEventListener != null) {
mSensorEventListener.setAllow180Rotation(allowed);
}
}
/**
* Gets the current orientation.
* @param lastRotation
* @return
*/
public int getCurrentRotation(int lastRotation) {
if (mEnabled) {
return mSensorEventListener.getCurrentRotation(lastRotation);
@@ -122,364 +130,6 @@ public abstract class WindowOrientationListener {
}
/**
* This class filters the raw accelerometer data and tries to detect actual changes in
* orientation. This is a very ill-defined problem so there are a lot of tweakable parameters,
* but here's the outline:
*
* - Convert the acceleromter vector from cartesian to spherical coordinates. Since we're
* dealing with rotation of the device, this is the sensible coordinate system to work in. The
* zenith direction is the Z-axis, i.e. the direction the screen is facing. The radial distance
* is referred to as the magnitude below. The elevation angle is referred to as the "tilt"
* below. The azimuth angle is referred to as the "orientation" below (and the azimuth axis is
* the Y-axis). See http://en.wikipedia.org/wiki/Spherical_coordinate_system for reference.
*
* - Low-pass filter the tilt and orientation angles to avoid "twitchy" behavior.
*
* - When the orientation angle reaches a certain threshold, transition to the corresponding
* orientation. These thresholds have some hysteresis built-in to avoid oscillation.
*
* - Use the magnitude to judge the accuracy of the data. Under ideal conditions, the magnitude
* should equal to that of gravity. When it differs significantly, we know the device is under
* external acceleration and we can't trust the data.
*
* - Use the tilt angle to judge the accuracy of orientation data. When the tilt angle is high
* in magnitude, we distrust the orientation data, because when the device is nearly flat, small
* physical movements produce large changes in orientation angle.
*
* Details are explained below.
*/
static class SensorEventListenerImpl implements SensorEventListener {
// We work with all angles in degrees in this class.
private static final float RADIANS_TO_DEGREES = (float) (180 / Math.PI);
// Indices into SensorEvent.values
private static final int _DATA_X = 0;
private static final int _DATA_Y = 1;
private static final int _DATA_Z = 2;
// Internal aliases for the four orientation states. ROTATION_0 = default portrait mode,
// ROTATION_90 = right side of device facing the sky, etc.
private static final int ROTATION_0 = 0;
private static final int ROTATION_90 = 1;
private static final int ROTATION_270 = 2;
private static final int ROTATION_180 = 3;
// Mapping our internal aliases into actual Surface rotation values
private static final int[] INTERNAL_TO_SURFACE_ROTATION = new int[] {
Surface.ROTATION_0, Surface.ROTATION_90, Surface.ROTATION_270,
Surface.ROTATION_180};
// Mapping Surface rotation values to internal aliases.
private static final int[] SURFACE_TO_INTERNAL_ROTATION = new int[] {
ROTATION_0, ROTATION_90, ROTATION_180, ROTATION_270};
// Threshold ranges of orientation angle to transition into other orientation states.
// The first list is for transitions from ROTATION_0, ROTATION_90, ROTATION_270,
// and then ROTATION_180.
// ROTATE_TO defines the orientation each threshold range transitions to, and must be kept
// in sync with this.
// We generally transition about the halfway point between two states with a swing of 30
// degrees for hysteresis.
private static final int[][][] THRESHOLDS = new int[][][] {
{{60, 180}, {180, 300}},
{{0, 30}, {195, 315}, {315, 360}},
{{0, 45}, {45, 165}, {330, 360}},
// Handle situation where we are currently doing 180 rotation
// but that is no longer allowed.
{{0, 45}, {45, 135}, {135, 225}, {225, 315}, {315, 360}},
};
// See THRESHOLDS
private static final int[][] ROTATE_TO = new int[][] {
{ROTATION_90, ROTATION_270},
{ROTATION_0, ROTATION_270, ROTATION_0},
{ROTATION_0, ROTATION_90, ROTATION_0},
{ROTATION_0, ROTATION_90, ROTATION_0, ROTATION_270, ROTATION_0},
};
// Thresholds that allow all 4 orientations.
private static final int[][][] THRESHOLDS_WITH_180 = new int[][][] {
{{60, 165}, {165, 195}, {195, 300}},
{{0, 30}, {165, 195}, {195, 315}, {315, 360}},
{{0, 45}, {45, 165}, {165, 195}, {330, 360}},
{{0, 45}, {45, 135}, {225, 315}, {315, 360}},
};
// See THRESHOLDS_WITH_180
private static final int[][] ROTATE_TO_WITH_180 = new int[][] {
{ROTATION_90, ROTATION_180, ROTATION_270},
{ROTATION_0, ROTATION_180, ROTATION_90, ROTATION_0},
{ROTATION_0, ROTATION_270, ROTATION_180, ROTATION_0},
{ROTATION_0, ROTATION_90, ROTATION_270, ROTATION_0},
};
// Maximum absolute tilt angle at which to consider orientation data. Beyond this (i.e.
// when screen is facing the sky or ground), we completely ignore orientation data.
private static final int MAX_TILT = 75;
// Additional limits on tilt angle to transition to each new orientation. We ignore all
// data with tilt beyond MAX_TILT, but we can set stricter limits on transitions to a
// particular orientation here.
private static final int[] MAX_TRANSITION_TILT = new int[] {MAX_TILT, 65, 65, 40};
// Between this tilt angle and MAX_TILT, we'll allow orientation changes, but we'll filter
// with a higher time constant, making us less sensitive to change. This primarily helps
// prevent momentary orientation changes when placing a device on a table from the side (or
// picking one up).
private static final int PARTIAL_TILT = 50;
// Maximum allowable deviation of the magnitude of the sensor vector from that of gravity,
// in m/s^2. Beyond this, we assume the phone is under external forces and we can't trust
// the sensor data. However, under constantly vibrating conditions (think car mount), we
// still want to pick up changes, so rather than ignore the data, we filter it with a very
// high time constant.
private static final float MAX_DEVIATION_FROM_GRAVITY = 1.5f;
// Minimum acceleration considered, in m/s^2. Below this threshold sensor noise will have
// significant impact on the calculations and in case of the vector (0, 0, 0) there is no
// defined rotation or tilt at all. Low or zero readings can happen when space travelling
// or free falling, but more commonly when shaking or getting bad readings from the sensor.
// The accelerometer is turned off when not used and polling it too soon after it is
// turned on may result in (0, 0, 0).
private static final float MIN_ABS_ACCELERATION = 1.5f;
// Actual sampling period corresponding to SensorManager.SENSOR_DELAY_NORMAL. There's no
// way to get this information from SensorManager.
// Note the actual period is generally 3-30ms larger than this depending on the device, but
// that's not enough to significantly skew our results.
private static final int SAMPLING_PERIOD_MS = 200;
// The following time constants are all used in low-pass filtering the accelerometer output.
// See http://en.wikipedia.org/wiki/Low-pass_filter#Discrete-time_realization for
// background.
// When device is near-vertical (screen approximately facing the horizon)
private static final int DEFAULT_TIME_CONSTANT_MS = 100;
// When device is partially tilted towards the sky or ground
private static final int TILTED_TIME_CONSTANT_MS = 500;
// When device is under external acceleration, i.e. not just gravity. We heavily distrust
// such readings.
private static final int ACCELERATING_TIME_CONSTANT_MS = 2000;
private static final float DEFAULT_LOWPASS_ALPHA =
computeLowpassAlpha(DEFAULT_TIME_CONSTANT_MS);
private static final float TILTED_LOWPASS_ALPHA =
computeLowpassAlpha(TILTED_TIME_CONSTANT_MS);
private static final float ACCELERATING_LOWPASS_ALPHA =
computeLowpassAlpha(ACCELERATING_TIME_CONSTANT_MS);
private boolean mAllow180Rotation = false;
private WindowOrientationListener mOrientationListener;
private int mRotation = ROTATION_0; // Current orientation state
private float mTiltAngle = 0; // low-pass filtered
private float mOrientationAngle = 0; // low-pass filtered
/*
* Each "distrust" counter represents our current level of distrust in the data based on
* a certain signal. For each data point that is deemed unreliable based on that signal,
* the counter increases; otherwise, the counter decreases. Exact rules vary.
*/
private int mAccelerationDistrust = 0; // based on magnitude != gravity
private int mTiltDistrust = 0; // based on tilt close to +/- 90 degrees
public SensorEventListenerImpl(WindowOrientationListener orientationListener) {
mOrientationListener = orientationListener;
}
private static float computeLowpassAlpha(int timeConstantMs) {
return (float) SAMPLING_PERIOD_MS / (timeConstantMs + SAMPLING_PERIOD_MS);
}
void setAllow180Rotation(boolean allowed) {
mAllow180Rotation = allowed;
}
int getCurrentRotation(int lastRotation) {
if (mTiltDistrust > 0) {
// we really don't know the current orientation, so trust what's currently displayed
mRotation = SURFACE_TO_INTERNAL_ROTATION[lastRotation];
}
return INTERNAL_TO_SURFACE_ROTATION[mRotation];
}
private void calculateNewRotation(float orientation, float tiltAngle) {
if (localLOGV) Log.i(TAG, orientation + ", " + tiltAngle + ", " + mRotation);
final boolean allow180Rotation = mAllow180Rotation;
int thresholdRanges[][] = allow180Rotation
? THRESHOLDS_WITH_180[mRotation] : THRESHOLDS[mRotation];
int row = -1;
for (int i = 0; i < thresholdRanges.length; i++) {
if (orientation >= thresholdRanges[i][0] && orientation < thresholdRanges[i][1]) {
row = i;
break;
}
}
if (row == -1) return; // no matching transition
int rotation = allow180Rotation
? ROTATE_TO_WITH_180[mRotation][row] : ROTATE_TO[mRotation][row];
if (tiltAngle > MAX_TRANSITION_TILT[rotation]) {
// tilted too far flat to go to this rotation
return;
}
if (localLOGV) Log.i(TAG, "orientation " + orientation + " gives new rotation = "
+ rotation);
mRotation = rotation;
mOrientationListener.onOrientationChanged(INTERNAL_TO_SURFACE_ROTATION[mRotation]);
}
private float lowpassFilter(float newValue, float oldValue, float alpha) {
return alpha * newValue + (1 - alpha) * oldValue;
}
private float vectorMagnitude(float x, float y, float z) {
return (float) Math.sqrt(x*x + y*y + z*z);
}
/**
* Angle between upVector and the x-y plane (the plane of the screen), in [-90, 90].
* +/- 90 degrees = screen facing the sky or ground.
*/
private float tiltAngle(float z, float magnitude) {
return (float) Math.asin(z / magnitude) * RADIANS_TO_DEGREES;
}
public void onSensorChanged(SensorEvent event) {
// the vector given in the SensorEvent points straight up (towards the sky) under ideal
// conditions (the phone is not accelerating). i'll call this upVector elsewhere.
float x = event.values[_DATA_X];
float y = event.values[_DATA_Y];
float z = event.values[_DATA_Z];
float magnitude = vectorMagnitude(x, y, z);
float deviation = Math.abs(magnitude - SensorManager.STANDARD_GRAVITY);
handleAccelerationDistrust(deviation);
if (magnitude < MIN_ABS_ACCELERATION) {
return; // Ignore tilt and orientation when (0, 0, 0) or low reading
}
// only filter tilt when we're accelerating
float alpha = 1;
if (mAccelerationDistrust > 0) {
alpha = ACCELERATING_LOWPASS_ALPHA;
}
float newTiltAngle = tiltAngle(z, magnitude);
mTiltAngle = lowpassFilter(newTiltAngle, mTiltAngle, alpha);
float absoluteTilt = Math.abs(mTiltAngle);
checkFullyTilted(absoluteTilt);
if (mTiltDistrust > 0) {
return; // when fully tilted, ignore orientation entirely
}
float newOrientationAngle = computeNewOrientation(x, y);
filterOrientation(absoluteTilt, newOrientationAngle);
calculateNewRotation(mOrientationAngle, absoluteTilt);
}
/**
* When accelerating, increment distrust; otherwise, decrement distrust. The idea is that
* if a single jolt happens among otherwise good data, we should keep trusting the good
* data. On the other hand, if a series of many bad readings comes in (as if the phone is
* being rapidly shaken), we should wait until things "settle down", i.e. we get a string
* of good readings.
*
* @param deviation absolute difference between the current magnitude and gravity
*/
private void handleAccelerationDistrust(float deviation) {
if (deviation > MAX_DEVIATION_FROM_GRAVITY) {
if (mAccelerationDistrust < 5) {
mAccelerationDistrust++;
}
} else if (mAccelerationDistrust > 0) {
mAccelerationDistrust--;
}
}
/**
* Check if the phone is tilted towards the sky or ground and handle that appropriately.
* When fully tilted, we automatically push the tilt up to a fixed value; otherwise we
* decrement it. The idea is to distrust the first few readings after the phone gets
* un-tilted, no matter what, i.e. preventing an accidental transition when the phone is
* picked up from a table.
*
* We also reset the orientation angle to the center of the current screen orientation.
* Since there is no real orientation of the phone, we want to ignore the most recent sensor
* data and reset it to this value to avoid a premature transition when the phone starts to
* get un-tilted.
*
* @param absoluteTilt the absolute value of the current tilt angle
*/
private void checkFullyTilted(float absoluteTilt) {
if (absoluteTilt > MAX_TILT) {
if (mRotation == ROTATION_0) {
mOrientationAngle = 0;
} else if (mRotation == ROTATION_90) {
mOrientationAngle = 90;
} else { // ROTATION_270
mOrientationAngle = 270;
}
if (mTiltDistrust < 3) {
mTiltDistrust = 3;
}
} else if (mTiltDistrust > 0) {
mTiltDistrust--;
}
}
/**
* Angle between the x-y projection of upVector and the +y-axis, increasing
* clockwise.
* 0 degrees = speaker end towards the sky
* 90 degrees = right edge of device towards the sky
*/
private float computeNewOrientation(float x, float y) {
float orientationAngle = (float) -Math.atan2(-x, y) * RADIANS_TO_DEGREES;
// atan2 returns [-180, 180]; normalize to [0, 360]
if (orientationAngle < 0) {
orientationAngle += 360;
}
return orientationAngle;
}
/**
* Compute a new filtered orientation angle.
*/
private void filterOrientation(float absoluteTilt, float orientationAngle) {
float alpha = DEFAULT_LOWPASS_ALPHA;
if (mAccelerationDistrust > 1) {
// when under more than a transient acceleration, distrust heavily
alpha = ACCELERATING_LOWPASS_ALPHA;
} else if (absoluteTilt > PARTIAL_TILT || mAccelerationDistrust == 1) {
// when tilted partway, or under transient acceleration, distrust lightly
alpha = TILTED_LOWPASS_ALPHA;
}
// since we're lowpass filtering a value with periodic boundary conditions, we need to
// adjust the new value to filter in the right direction...
float deltaOrientation = orientationAngle - mOrientationAngle;
if (deltaOrientation > 180) {
orientationAngle -= 360;
} else if (deltaOrientation < -180) {
orientationAngle += 360;
}
mOrientationAngle = lowpassFilter(orientationAngle, mOrientationAngle, alpha);
// ...and then adjust back to ensure we're in the range [0, 360]
if (mOrientationAngle > 360) {
mOrientationAngle -= 360;
} else if (mOrientationAngle < 0) {
mOrientationAngle += 360;
}
}
public void onAccuracyChanged(Sensor sensor, int accuracy) {
}
}
/*
* Returns true if sensor is enabled and false otherwise
*/
public boolean canDetectOrientation() {
@@ -492,5 +142,481 @@ public abstract class WindowOrientationListener {
* @param rotation The new orientation of the device, one of the Surface.ROTATION_* constants.
* @see Surface
*/
abstract public void onOrientationChanged(int rotation);
public abstract void onOrientationChanged(int rotation);
/**
* Enables or disables the window orientation listener logging for use with
* the orientationplot.py tool.
* Logging is usually enabled via Development Settings. (See class comments.)
* @param enable True to enable logging.
*/
public void setLogEnabled(boolean enable) {
mLogEnabled = enable;
}
/**
* This class filters the raw accelerometer data and tries to detect actual changes in
* orientation. This is a very ill-defined problem so there are a lot of tweakable parameters,
* but here's the outline:
*
* - Low-pass filter the accelerometer vector in cartesian coordinates. We do it in
* cartesian space because the orientation calculations are sensitive to the
* absolute magnitude of the acceleration. In particular, there are singularities
* in the calculation as the magnitude approaches 0. By performing the low-pass
* filtering early, we can eliminate high-frequency impulses systematically.
*
* - Convert the acceleromter vector from cartesian to spherical coordinates.
* Since we're dealing with rotation of the device, this is the sensible coordinate
* system to work in. The zenith direction is the Z-axis, the direction the screen
* is facing. The radial distance is referred to as the magnitude below.
* The elevation angle is referred to as the "tilt" below.
* The azimuth angle is referred to as the "orientation" below (and the azimuth axis is
* the Y-axis).
* See http://en.wikipedia.org/wiki/Spherical_coordinate_system for reference.
*
* - If the tilt angle is too close to horizontal (near 90 or -90 degrees), do nothing.
* The orientation angle is not meaningful when the device is nearly horizontal.
* The tilt angle thresholds are set differently for each orientation and different
* limits are applied when the device is facing down as opposed to when it is facing
* forward or facing up.
*
* - When the orientation angle reaches a certain threshold, consider transitioning
* to the corresponding orientation. These thresholds have some hysteresis built-in
* to avoid oscillations between adjacent orientations.
*
* - Use the magnitude to judge the confidence of the orientation.
* Under ideal conditions, the magnitude should equal to that of gravity. When it
* differs significantly, we know the device is under external acceleration and
* we can't trust the data.
*
* - Use the tilt angle to judge the confidence of the orientation.
* When the tilt angle is high in absolute value then the device is nearly flat
* so small physical movements produce large changes in orientation angle.
* This can be the case when the device is being picked up from a table.
*
* - Use the orientation angle to judge the confidence of the orientation.
* The close the orientation angle is to the canonical orientation angle, the better.
*
* - Based on the aggregate confidence, we determine how long we want to wait for
* the new orientation to settle. This is accomplished by integrating the confidence
* for each orientation over time. When a threshold integration sum is reached
* then we actually change orientations.
*
* Details are explained inline.
*/
static final class SensorEventListenerImpl implements SensorEventListener {
// We work with all angles in degrees in this class.
private static final float RADIANS_TO_DEGREES = (float) (180 / Math.PI);
// Indices into SensorEvent.values for the accelerometer sensor.
private static final int ACCELEROMETER_DATA_X = 0;
private static final int ACCELEROMETER_DATA_Y = 1;
private static final int ACCELEROMETER_DATA_Z = 2;
// Rotation constants.
// These are the same as Surface rotation constants with the addition of a 5th
// unknown state when we are not confident about the proporsed orientation.
// One important property of these constants is that they are equal to the
// orientation angle itself divided by 90. We use this fact to map
// back and forth between orientation angles and rotation values.
private static final int ROTATION_UNKNOWN = -1;
//private static final int ROTATION_0 = Surface.ROTATION_0; // 0
//private static final int ROTATION_90 = Surface.ROTATION_90; // 1
//private static final int ROTATION_180 = Surface.ROTATION_180; // 2
//private static final int ROTATION_270 = Surface.ROTATION_270; // 3
private final WindowOrientationListener mOrientationListener;
private int mRotation = ROTATION_UNKNOWN;
/* State for first order low-pass filtering of accelerometer data.
* See http://en.wikipedia.org/wiki/Low-pass_filter#Discrete-time_realization for
* signal processing background.
*/
private long mLastTimestamp = Long.MAX_VALUE; // in nanoseconds
private float mLastFilteredX, mLastFilteredY, mLastFilteredZ;
// The maximum sample inter-arrival time in milliseconds.
// If the acceleration samples are further apart than this amount in time, we reset the
// state of the low-pass filter and orientation properties. This helps to handle
// boundary conditions when the device is turned on, wakes from suspend or there is
// a significant gap in samples.
private static final float MAX_FILTER_DELTA_TIME_MS = 1000;
// The acceleration filter cutoff frequency.
// This is the frequency at which signals are attenuated by 3dB (half the passband power).
// Each successive octave beyond this frequency is attenuated by an additional 6dB.
//
// We choose the cutoff frequency such that impulses and vibrational noise
// (think car dock) is suppressed. However, this filtering does not eliminate
// all possible sources of orientation ambiguity so we also rely on a dynamic
// settle time for establishing a new orientation. Filtering adds latency
// inversely proportional to the cutoff frequency so we don't want to make
// it too small or we can lose hundreds of milliseconds of responsiveness.
private static final float FILTER_CUTOFF_FREQUENCY_HZ = 1f;
private static final float FILTER_TIME_CONSTANT_MS = (float)(500.0f
/ (Math.PI * FILTER_CUTOFF_FREQUENCY_HZ)); // t = 1 / (2pi * Fc) * 1000ms
// The filter gain.
// We choose a value slightly less than unity to avoid numerical instabilities due
// to floating-point error accumulation.
private static final float FILTER_GAIN = 0.999f;
/* State for orientation detection. */
// Thresholds for minimum and maximum allowable deviation from gravity.
//
// If the device is undergoing external acceleration (being bumped, in a car
// that is turning around a corner or a plane taking off) then the magnitude
// may be substantially more or less than gravity. This can skew our orientation
// detection by making us think that up is pointed in a different direction.
//
// Conversely, if the device is in freefall, then there will be no gravity to
// measure at all. This is problematic because we cannot detect the orientation
// without gravity to tell us which way is up. A magnitude near 0 produces
// singularities in the tilt and orientation calculations.
//
// In both cases, we postpone choosing an orientation.
private static final float MIN_ACCELERATION_MAGNITUDE =
SensorManager.STANDARD_GRAVITY * 0.5f;
private static final float MAX_ACCELERATION_MAGNITUDE =
SensorManager.STANDARD_GRAVITY * 1.5f;
// Maximum absolute tilt angle at which to consider orientation data. Beyond this (i.e.
// when screen is facing the sky or ground), we completely ignore orientation data.
private static final int MAX_TILT = 75;
// The tilt angle range in degrees for each orientation.
// Beyond these tilt angles, we don't even consider transitioning into the
// specified orientation. We place more stringent requirements on unnatural
// orientations than natural ones to make it less likely to accidentally transition
// into those states.
// The first value of each pair is negative so it applies a limit when the device is
// facing down (overhead reading in bed).
// The second value of each pair is positive so it applies a limit when the device is
// facing up (resting on a table).
// The ideal tilt angle is 0 (when the device is vertical) so the limits establish
// how close to vertical the device must be in order to change orientation.
private static final int[][] TILT_TOLERANCE = new int[][] {
/* ROTATION_0 */ { -20, 75 },
/* ROTATION_90 */ { -20, 70 },
/* ROTATION_180 */ { -20, 65 },
/* ROTATION_270 */ { -20, 70 }
};
// The gap angle in degrees between adjacent orientation angles for hysteresis.
// This creates a "dead zone" between the current orientation and a proposed
// adjacent orientation. No orientation proposal is made when the orientation
// angle is within the gap between the current orientation and the adjacent
// orientation.
private static final int ADJACENT_ORIENTATION_ANGLE_GAP = 30;
// The confidence scale factors for angle, tilt and magnitude.
// When the distance between the actual value and the ideal value is the
// specified delta, orientation transitions will take twice as long as they would
// in the ideal case. Increasing or decreasing the delta has an exponential effect
// on each factor's influence over the transition time.
// Transition takes 2x longer when angle is 30 degrees from ideal orientation angle.
private static final float ORIENTATION_ANGLE_CONFIDENCE_SCALE =
confidenceScaleFromDelta(30);
// Transition takes 2x longer when tilt is 45 degrees from vertical.
private static final float TILT_ANGLE_CONFIDENCE_SCALE = confidenceScaleFromDelta(45);
// Transition takes 2x longer when acceleration is 0.25 Gs.
private static final float MAGNITUDE_CONFIDENCE_SCALE = confidenceScaleFromDelta(
SensorManager.STANDARD_GRAVITY * 0.25f);
// The number of milliseconds for which a new orientation must be stable before
// we perform an orientation change under ideal conditions. It will take
// proportionally longer than this to effect an orientation change when
// the proposed orientation confidence is low.
private static final float ORIENTATION_SETTLE_TIME_MS = 250;
// The confidence that we have abount effecting each orientation change.
// When one of these values exceeds 1.0, we have determined our new orientation!
private float mConfidence[] = new float[4];
public SensorEventListenerImpl(WindowOrientationListener orientationListener) {
mOrientationListener = orientationListener;
}
public int getCurrentRotation(int lastRotation) {
return mRotation != ROTATION_UNKNOWN ? mRotation : lastRotation;
}
@Override
public void onAccuracyChanged(Sensor sensor, int accuracy) {
}
@Override
public void onSensorChanged(SensorEvent event) {
final boolean log = mOrientationListener.mLogEnabled;
// The vector given in the SensorEvent points straight up (towards the sky) under ideal
// conditions (the phone is not accelerating). I'll call this up vector elsewhere.
float x = event.values[ACCELEROMETER_DATA_X];
float y = event.values[ACCELEROMETER_DATA_Y];
float z = event.values[ACCELEROMETER_DATA_Z];
if (log) {
Slog.v(TAG, "Raw acceleration vector: " +
"x=" + x + ", y=" + y + ", z=" + z);
}
// Apply a low-pass filter to the acceleration up vector in cartesian space.
// Reset the orientation listener state if the samples are too far apart in time
// or when we see values of (0, 0, 0) which indicates that we polled the
// accelerometer too soon after turning it on and we don't have any data yet.
final float timeDeltaMS = (event.timestamp - mLastTimestamp) * 0.000001f;
boolean skipSample;
if (timeDeltaMS <= 0 || timeDeltaMS > MAX_FILTER_DELTA_TIME_MS
|| (x == 0 && y == 0 && z == 0)) {
if (log) {
Slog.v(TAG, "Resetting orientation listener.");
}
for (int i = 0; i < 4; i++) {
mConfidence[i] = 0;
}
skipSample = true;
} else {
final float alpha = timeDeltaMS
/ (FILTER_TIME_CONSTANT_MS + timeDeltaMS) * FILTER_GAIN;
x = alpha * (x - mLastFilteredX) + mLastFilteredX;
y = alpha * (y - mLastFilteredY) + mLastFilteredY;
z = alpha * (z - mLastFilteredZ) + mLastFilteredZ;
if (log) {
Slog.v(TAG, "Filtered acceleration vector: " +
"x=" + x + ", y=" + y + ", z=" + z);
}
skipSample = false;
}
mLastTimestamp = event.timestamp;
mLastFilteredX = x;
mLastFilteredY = y;
mLastFilteredZ = z;
boolean orientationChanged = false;
if (!skipSample) {
// Determine a proposed orientation based on the currently available data.
int proposedOrientation = ROTATION_UNKNOWN;
float combinedConfidence = 1.0f;
// Calculate the magnitude of the acceleration vector.
final float magnitude = (float) Math.sqrt(x * x + y * y + z * z);
if (magnitude < MIN_ACCELERATION_MAGNITUDE
|| magnitude > MAX_ACCELERATION_MAGNITUDE) {
if (log) {
Slog.v(TAG, "Ignoring sensor data, magnitude out of range: "
+ "magnitude=" + magnitude);
}
} else {
// Calculate the tilt angle.
// This is the angle between the up vector and the x-y plane (the plane of
// the screen) in a range of [-90, 90] degrees.
// -90 degrees: screen horizontal and facing the ground (overhead)
// 0 degrees: screen vertical
// 90 degrees: screen horizontal and facing the sky (on table)
final int tiltAngle = (int) Math.round(
Math.asin(z / magnitude) * RADIANS_TO_DEGREES);
// If the tilt angle is too close to horizontal then we cannot determine
// the orientation angle of the screen.
if (Math.abs(tiltAngle) > MAX_TILT) {
if (log) {
Slog.v(TAG, "Ignoring sensor data, tilt angle too high: "
+ "magnitude=" + magnitude + ", tiltAngle=" + tiltAngle);
}
} else {
// Calculate the orientation angle.
// This is the angle between the x-y projection of the up vector onto
// the +y-axis, increasing clockwise in a range of [0, 360] degrees.
int orientationAngle = (int) Math.round(
-Math.atan2(-x, y) * RADIANS_TO_DEGREES);
if (orientationAngle < 0) {
// atan2 returns [-180, 180]; normalize to [0, 360]
orientationAngle += 360;
}
// Find the nearest orientation.
// An orientation of 0 can have a nearest angle of 0 or 360 depending
// on which is closer to the measured orientation angle. We leave the
// nearest angle at 360 in that case since it makes the delta calculation
// for orientation angle confidence easier below.
int nearestOrientation = (orientationAngle + 45) / 90;
int nearestOrientationAngle = nearestOrientation * 90;
if (nearestOrientation == 4) {
nearestOrientation = 0;
}
// Determine the proposed orientation.
// The confidence of the proposal is 1.0 when it is ideal and it
// decays exponentially as the proposal moves further from the ideal
// angle, tilt and magnitude of the proposed orientation.
if (isTiltAngleAcceptable(nearestOrientation, tiltAngle)
&& isOrientationAngleAcceptable(nearestOrientation,
orientationAngle)) {
proposedOrientation = nearestOrientation;
final float idealOrientationAngle = nearestOrientationAngle;
final float orientationConfidence = confidence(orientationAngle,
idealOrientationAngle, ORIENTATION_ANGLE_CONFIDENCE_SCALE);
final float idealTiltAngle = 0;
final float tiltConfidence = confidence(tiltAngle,
idealTiltAngle, TILT_ANGLE_CONFIDENCE_SCALE);
final float idealMagnitude = SensorManager.STANDARD_GRAVITY;
final float magnitudeConfidence = confidence(magnitude,
idealMagnitude, MAGNITUDE_CONFIDENCE_SCALE);
combinedConfidence = orientationConfidence
* tiltConfidence * magnitudeConfidence;
if (log) {
Slog.v(TAG, "Proposal: "
+ "magnitude=" + magnitude
+ ", tiltAngle=" + tiltAngle
+ ", orientationAngle=" + orientationAngle
+ ", proposedOrientation=" + proposedOrientation
+ ", combinedConfidence=" + combinedConfidence
+ ", orientationConfidence=" + orientationConfidence
+ ", tiltConfidence=" + tiltConfidence
+ ", magnitudeConfidence=" + magnitudeConfidence);
}
} else {
if (log) {
Slog.v(TAG, "Ignoring sensor data, no proposal: "
+ "magnitude=" + magnitude + ", tiltAngle=" + tiltAngle
+ ", orientationAngle=" + orientationAngle);
}
}
}
}
// Sum up the orientation confidence weights.
// Detect an orientation change when the sum reaches 1.0.
final float confidenceAmount = combinedConfidence * timeDeltaMS
/ ORIENTATION_SETTLE_TIME_MS;
for (int i = 0; i < 4; i++) {
if (i == proposedOrientation) {
mConfidence[i] += confidenceAmount;
if (mConfidence[i] >= 1.0f) {
mConfidence[i] = 1.0f;
if (i != mRotation) {
if (log) {
Slog.v(TAG, "Orientation changed! rotation=" + i);
}
mRotation = i;
orientationChanged = true;
}
}
} else {
mConfidence[i] -= confidenceAmount;
if (mConfidence[i] < 0.0f) {
mConfidence[i] = 0.0f;
}
}
}
}
// Write final statistics about where we are in the orientation detection process.
if (log) {
Slog.v(TAG, "Result: rotation=" + mRotation
+ ", confidence=["
+ mConfidence[0] + ", "
+ mConfidence[1] + ", "
+ mConfidence[2] + ", "
+ mConfidence[3] + "], timeDeltaMS=" + timeDeltaMS);
}
// Tell the listener.
if (orientationChanged) {
mOrientationListener.onOrientationChanged(mRotation);
}
}
/**
* Returns true if the tilt angle is acceptable for a proposed
* orientation transition.
*/
private boolean isTiltAngleAcceptable(int proposedOrientation,
int tiltAngle) {
return tiltAngle >= TILT_TOLERANCE[proposedOrientation][0]
&& tiltAngle <= TILT_TOLERANCE[proposedOrientation][1];
}
/**
* Returns true if the orientation angle is acceptable for a proposed
* orientation transition.
* This function takes into account the gap between adjacent orientations
* for hysteresis.
*/
private boolean isOrientationAngleAcceptable(int proposedOrientation,
int orientationAngle) {
final int currentOrientation = mRotation;
// If there is no current rotation, then there is no gap.
if (currentOrientation != ROTATION_UNKNOWN) {
// If the proposed orientation is the same or is counter-clockwise adjacent,
// then we set a lower bound on the orientation angle.
// For example, if currentOrientation is ROTATION_0 and proposed is ROTATION_90,
// then we want to check orientationAngle > 45 + GAP / 2.
if (proposedOrientation == currentOrientation
|| proposedOrientation == (currentOrientation + 1) % 4) {
int lowerBound = proposedOrientation * 90 - 45
+ ADJACENT_ORIENTATION_ANGLE_GAP / 2;
if (proposedOrientation == 0) {
if (orientationAngle >= 315 && orientationAngle < lowerBound + 360) {
return false;
}
} else {
if (orientationAngle < lowerBound) {
return false;
}
}
}
// If the proposed orientation is the same or is clockwise adjacent,
// then we set an upper bound on the orientation angle.
// For example, if currentOrientation is ROTATION_0 and proposed is ROTATION_270,
// then we want to check orientationAngle < 315 - GAP / 2.
if (proposedOrientation == currentOrientation
|| proposedOrientation == (currentOrientation + 3) % 4) {
int upperBound = proposedOrientation * 90 + 45
- ADJACENT_ORIENTATION_ANGLE_GAP / 2;
if (proposedOrientation == 0) {
if (orientationAngle <= 45 && orientationAngle > upperBound) {
return false;
}
} else {
if (orientationAngle > upperBound) {
return false;
}
}
}
}
return true;
}
/**
* Calculate an exponentially weighted confidence value in the range [0.0, 1.0].
* The further the value is from the target, the more the confidence trends to 0.
*/
private static float confidence(float value, float target, float scale) {
return (float) Math.exp(-Math.abs(value - target) * scale);
}
/**
* Calculate a scale factor for the confidence weight exponent.
* The scale value is chosen such that confidence(value, target, scale) == 0.5
* whenever abs(value - target) == cutoffDelta.
*/
private static float confidenceScaleFromDelta(float cutoffDelta) {
return (float) -Math.log(0.5) / cutoffDelta;
}
}
}

View File

@@ -388,6 +388,8 @@ public class PhoneWindowManager implements WindowManagerPolicy {
Settings.System.USER_ROTATION), false, this);
resolver.registerContentObserver(Settings.System.getUriFor(
Settings.System.SCREEN_OFF_TIMEOUT), false, this);
resolver.registerContentObserver(Settings.System.getUriFor(
Settings.System.WINDOW_ORIENTATION_LISTENER_LOG), false, this);
resolver.registerContentObserver(Settings.System.getUriFor(
Settings.System.POINTER_LOCATION), false, this);
resolver.registerContentObserver(Settings.Secure.getUriFor(
@@ -759,6 +761,10 @@ public class PhoneWindowManager implements WindowManagerPolicy {
updateOrientationListenerLp();
}
mOrientationListener.setLogEnabled(
Settings.System.getInt(resolver,
Settings.System.WINDOW_ORIENTATION_LISTENER_LOG, 0) != 0);
if (mSystemReady) {
int pointerLocation = Settings.System.getInt(resolver,
Settings.System.POINTER_LOCATION, 0);
@@ -2492,18 +2498,11 @@ public class PhoneWindowManager implements WindowManagerPolicy {
return mSeascapeRotation;
case ActivityInfo.SCREEN_ORIENTATION_SENSOR_LANDSCAPE:
//return either landscape rotation based on the sensor
mOrientationListener.setAllow180Rotation(
isLandscapeOrSeascape(Surface.ROTATION_180));
return getCurrentLandscapeRotation(lastRotation);
case ActivityInfo.SCREEN_ORIENTATION_SENSOR_PORTRAIT:
mOrientationListener.setAllow180Rotation(
!isLandscapeOrSeascape(Surface.ROTATION_180));
return getCurrentPortraitRotation(lastRotation);
}
mOrientationListener.setAllow180Rotation(mAllowAllRotations ||
orientation == ActivityInfo.SCREEN_ORIENTATION_FULL_SENSOR);
// case for nosensor meaning ignore sensor and consider only lid
// or orientation sensor disabled
//or case.unspecified
@@ -2519,7 +2518,15 @@ public class PhoneWindowManager implements WindowManagerPolicy {
return mUserRotation;
} else {
if (useSensorForOrientationLp(orientation)) {
return mOrientationListener.getCurrentRotation(lastRotation);
// Disable 180 degree rotation unless allowed by default for the device
// or explicitly requested by the application.
int rotation = mOrientationListener.getCurrentRotation(lastRotation);
if (rotation == Surface.ROTATION_180
&& !mAllowAllRotations
&& orientation != ActivityInfo.SCREEN_ORIENTATION_FULL_SENSOR) {
return lastRotation;
}
return rotation;
}
return Surface.ROTATION_0;
}

View File

@@ -0,0 +1,87 @@
This directory contains a simple python script for visualizing
the behavior of the WindowOrientationListener.
PREREQUISITES
-------------
1. Python 2.6
2. numpy
3. matplotlib
USAGE
-----
The tool works by scaping the debug log output from WindowOrientationListener
for interesting data and then plotting it.
1. Enable the Window Orientation Listener debugging data log using the
Development Settings in the Dev Tools application (Development.apk).
2. Plug in the device. Ensure that it is the only device plugged in
since this script is of very little brain and will get confused otherwise.
3. Run "orientationplot.py".
4. When finished, remember to disable the debug log output since it is quite verbose!
WHAT IT ALL MEANS
-----------------
The tool displays several time series graphs that plot the output of the
WindowOrientationListener. Here you can see the raw accelerometer data,
filtered accelerometer data, measured tilt and orientation angle, confidence
intervals for the proposed orientation and accelerometer latency.
Things to look for:
1. Ensure the filtering is not too aggressive. If the filter cut-off frequency is
less than about 1Hz, then the filtered accelorometer data becomes too smooth
and the latency for orientation detection goes up. One way to observe this
is by holding the device vertically in one orientation then sharply turning
it 90 degrees to a different orientation. Compared the rapid changes in the
raw accelerometer data with the smoothed out filtered data. If the filtering
is too aggressive, the filter response may lag by hundreds of milliseconds.
2. Ensure that there is an appropriate gap between adjacent orientation angles
for hysteresis. Try holding the device in one orientation and slowly turning
it 90 degrees. Note that the confidence intervals will all drop to 0 at some
point in between the two orientations; that is the gap. The gap should be
observed between all adjacent pairs of orientations when turning the device
in either direction.
Next try holding the device in one orientation and rapidly turning it end
over end to a midpoint about 45 degrees between two opposing orientations.
There should be no gap observed initially. The algorithm should pick one
of the orientations and settle into it (since it is obviously quite
different from the original orientation of the device). However, once it
settles, the confidence values should start trending to 0 again because
the measured orientation angle is now within the gap between the new
orientation and the adjacent orientation.
In other words, the hysteresis gap applies only when the measured orientation
angle (say, 45 degrees) is between the current orientation's ideal angle
(say, 0 degrees) and an adjacent orientation's ideal angle (say, 90 degrees).
3. Accelerometer jitter. The accelerometer latency graph displays the interval
between sensor events as reported by the SensorEvent.timestamp field. It
should be a fairly constant 60ms. If the latency jumps around wildly or
greatly exceeds 60ms then there is a problem with the accelerometer or the
sensor manager.
4. The orientation angle is not measured when the tilt is too close to 90 or -90
degrees (refer to MAX_TILT constant). Consequently, you should expect there
to be no data. Likewise, all dependent calculations are suppressed in this case
so there will be no orientation proposal either.
5. Each orientation has its own bound on allowable tilt angles. It's a good idea to
verify that these limits are being enforced by gradually varying the tilt of
the device until it is inside/outside the limit for each orientation.
6. Orientation changes should be significantly harder when the device is held
overhead. People reading on tablets in bed often have their head turned
a little to the side, or they hold the device loosely so its orientation
can be a bit unusual. The tilt is a good indicator of whether the device is
overhead.

View File

@@ -0,0 +1,451 @@
#!/usr/bin/env python2.6
#
# Copyright (C) 2011 The Android Open Source Project
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
#
# Plots debug log output from WindowOrientationListener.
# See README.txt for details.
#
import numpy as np
import matplotlib.pyplot as plot
import subprocess
import re
import fcntl
import os
import errno
import bisect
from datetime import datetime, timedelta
# Parameters.
timespan = 15 # seconds total span shown
scrolljump = 5 # seconds jump when scrolling
timeticks = 1 # seconds between each time tick
# Non-blocking stream wrapper.
class NonBlockingStream:
def __init__(self, stream):
fcntl.fcntl(stream, fcntl.F_SETFL, os.O_NONBLOCK)
self.stream = stream
self.buffer = ''
self.pos = 0
def readline(self):
while True:
index = self.buffer.find('\n', self.pos)
if index != -1:
result = self.buffer[self.pos:index]
self.pos = index + 1
return result
self.buffer = self.buffer[self.pos:]
self.pos = 0
try:
chunk = os.read(self.stream.fileno(), 4096)
except OSError, e:
if e.errno == errno.EAGAIN:
return None
raise e
if len(chunk) == 0:
if len(self.buffer) == 0:
raise(EOFError)
else:
result = self.buffer
self.buffer = ''
self.pos = 0
return result
self.buffer += chunk
# Plotter
class Plotter:
def __init__(self, adbout):
self.adbout = adbout
self.fig = plot.figure(1)
self.fig.suptitle('Window Orientation Listener', fontsize=12)
self.fig.set_dpi(96)
self.fig.set_size_inches(16, 12, forward=True)
self.raw_acceleration_x = self._make_timeseries()
self.raw_acceleration_y = self._make_timeseries()
self.raw_acceleration_z = self._make_timeseries()
self.raw_acceleration_axes = self._add_timeseries_axes(
1, 'Raw Acceleration', 'm/s^2', [-20, 20],
yticks=range(-15, 16, 5))
self.raw_acceleration_line_x = self._add_timeseries_line(
self.raw_acceleration_axes, 'x', 'red')
self.raw_acceleration_line_y = self._add_timeseries_line(
self.raw_acceleration_axes, 'y', 'green')
self.raw_acceleration_line_z = self._add_timeseries_line(
self.raw_acceleration_axes, 'z', 'blue')
self._add_timeseries_legend(self.raw_acceleration_axes)
shared_axis = self.raw_acceleration_axes
self.filtered_acceleration_x = self._make_timeseries()
self.filtered_acceleration_y = self._make_timeseries()
self.filtered_acceleration_z = self._make_timeseries()
self.magnitude = self._make_timeseries()
self.filtered_acceleration_axes = self._add_timeseries_axes(
2, 'Filtered Acceleration', 'm/s^2', [-20, 20],
sharex=shared_axis,
yticks=range(-15, 16, 5))
self.filtered_acceleration_line_x = self._add_timeseries_line(
self.filtered_acceleration_axes, 'x', 'red')
self.filtered_acceleration_line_y = self._add_timeseries_line(
self.filtered_acceleration_axes, 'y', 'green')
self.filtered_acceleration_line_z = self._add_timeseries_line(
self.filtered_acceleration_axes, 'z', 'blue')
self.magnitude_line = self._add_timeseries_line(
self.filtered_acceleration_axes, 'magnitude', 'orange', linewidth=2)
self._add_timeseries_legend(self.filtered_acceleration_axes)
self.tilt_angle = self._make_timeseries()
self.tilt_angle_axes = self._add_timeseries_axes(
3, 'Tilt Angle', 'degrees', [-105, 105],
sharex=shared_axis,
yticks=range(-90, 91, 30))
self.tilt_angle_line = self._add_timeseries_line(
self.tilt_angle_axes, 'tilt', 'black')
self._add_timeseries_legend(self.tilt_angle_axes)
self.orientation_angle = self._make_timeseries()
self.orientation_angle_axes = self._add_timeseries_axes(
4, 'Orientation Angle', 'degrees', [-25, 375],
sharex=shared_axis,
yticks=range(0, 361, 45))
self.orientation_angle_line = self._add_timeseries_line(
self.orientation_angle_axes, 'orientation', 'black')
self._add_timeseries_legend(self.orientation_angle_axes)
self.actual_orientation = self._make_timeseries()
self.proposed_orientation = self._make_timeseries()
self.orientation_axes = self._add_timeseries_axes(
5, 'Actual / Proposed Orientation and Confidence', 'rotation', [-1, 4],
sharex=shared_axis,
yticks=range(0, 4))
self.actual_orientation_line = self._add_timeseries_line(
self.orientation_axes, 'actual', 'black', linewidth=2)
self.proposed_orientation_line = self._add_timeseries_line(
self.orientation_axes, 'proposed', 'purple', linewidth=3)
self._add_timeseries_legend(self.orientation_axes)
self.confidence = [[self._make_timeseries(), self._make_timeseries()] for i in range(0, 4)]
self.confidence_polys = []
self.combined_confidence = self._make_timeseries()
self.orientation_confidence = self._make_timeseries()
self.tilt_confidence = self._make_timeseries()
self.magnitude_confidence = self._make_timeseries()
self.confidence_axes = self._add_timeseries_axes(
6, 'Proposed Orientation Confidence Factors', 'confidence', [-0.1, 1.1],
sharex=shared_axis,
yticks=[0.0, 0.2, 0.4, 0.6, 0.8, 1.0])
self.combined_confidence_line = self._add_timeseries_line(
self.confidence_axes, 'combined', 'purple', linewidth=2)
self.orientation_confidence_line = self._add_timeseries_line(
self.confidence_axes, 'orientation', 'black')
self.tilt_confidence_line = self._add_timeseries_line(
self.confidence_axes, 'tilt', 'brown')
self.magnitude_confidence_line = self._add_timeseries_line(
self.confidence_axes, 'magnitude', 'orange')
self._add_timeseries_legend(self.confidence_axes)
self.sample_latency = self._make_timeseries()
self.sample_latency_axes = self._add_timeseries_axes(
7, 'Accelerometer Sampling Latency', 'ms', [-10, 500],
sharex=shared_axis,
yticks=range(0, 500, 100))
self.sample_latency_line = self._add_timeseries_line(
self.sample_latency_axes, 'latency', 'black')
self._add_timeseries_legend(self.sample_latency_axes)
self.timer = self.fig.canvas.new_timer(interval=100)
self.timer.add_callback(lambda: self.update())
self.timer.start()
self.timebase = None
self._reset_parse_state()
# Initialize a time series.
def _make_timeseries(self):
return [[], []]
# Add a subplot to the figure for a time series.
def _add_timeseries_axes(self, index, title, ylabel, ylim, yticks, sharex=None):
num_graphs = 7
height = 0.9 / num_graphs
top = 0.95 - height * index
axes = self.fig.add_axes([0.1, top, 0.8, height],
xscale='linear',
xlim=[0, timespan],
ylabel=ylabel,
yscale='linear',
ylim=ylim,
sharex=sharex)
axes.text(0.02, 0.02, title, transform=axes.transAxes, fontsize=10, fontweight='bold')
axes.set_xlabel('time (s)', fontsize=10, fontweight='bold')
axes.set_ylabel(ylabel, fontsize=10, fontweight='bold')
axes.set_xticks(range(0, timespan + 1, timeticks))
axes.set_yticks(yticks)
axes.grid(True)
for label in axes.get_xticklabels():
label.set_fontsize(9)
for label in axes.get_yticklabels():
label.set_fontsize(9)
return axes
# Add a line to the axes for a time series.
def _add_timeseries_line(self, axes, label, color, linewidth=1):
return axes.plot([], label=label, color=color, linewidth=linewidth)[0]
# Add a legend to a time series.
def _add_timeseries_legend(self, axes):
axes.legend(
loc='upper left',
bbox_to_anchor=(1.01, 1),
borderpad=0.1,
borderaxespad=0.1,
prop={'size': 10})
# Resets the parse state.
def _reset_parse_state(self):
self.parse_raw_acceleration_x = None
self.parse_raw_acceleration_y = None
self.parse_raw_acceleration_z = None
self.parse_filtered_acceleration_x = None
self.parse_filtered_acceleration_y = None
self.parse_filtered_acceleration_z = None
self.parse_magnitude = None
self.parse_tilt_angle = None
self.parse_orientation_angle = None
self.parse_proposed_orientation = None
self.parse_combined_confidence = None
self.parse_orientation_confidence = None
self.parse_tilt_confidence = None
self.parse_magnitude_confidence = None
self.parse_actual_orientation = None
self.parse_confidence = None
self.parse_sample_latency = None
# Update samples.
def update(self):
timeindex = 0
while True:
try:
line = self.adbout.readline()
except EOFError:
plot.close()
return
if line is None:
break
print line
try:
timestamp = self._parse_timestamp(line)
except ValueError, e:
continue
if self.timebase is None:
self.timebase = timestamp
delta = timestamp - self.timebase
timeindex = delta.seconds + delta.microseconds * 0.000001
if line.find('Raw acceleration vector:') != -1:
self.parse_raw_acceleration_x = self._get_following_number(line, 'x=')
self.parse_raw_acceleration_y = self._get_following_number(line, 'y=')
self.parse_raw_acceleration_z = self._get_following_number(line, 'z=')
if line.find('Filtered acceleration vector:') != -1:
self.parse_filtered_acceleration_x = self._get_following_number(line, 'x=')
self.parse_filtered_acceleration_y = self._get_following_number(line, 'y=')
self.parse_filtered_acceleration_z = self._get_following_number(line, 'z=')
if line.find('magnitude=') != -1:
self.parse_magnitude = self._get_following_number(line, 'magnitude=')
if line.find('tiltAngle=') != -1:
self.parse_tilt_angle = self._get_following_number(line, 'tiltAngle=')
if line.find('orientationAngle=') != -1:
self.parse_orientation_angle = self._get_following_number(line, 'orientationAngle=')
if line.find('Proposal:') != -1:
self.parse_proposed_orientation = self._get_following_number(line, 'proposedOrientation=')
self.parse_combined_confidence = self._get_following_number(line, 'combinedConfidence=')
self.parse_orientation_confidence = self._get_following_number(line, 'orientationConfidence=')
self.parse_tilt_confidence = self._get_following_number(line, 'tiltConfidence=')
self.parse_magnitude_confidence = self._get_following_number(line, 'magnitudeConfidence=')
if line.find('Result:') != -1:
self.parse_actual_orientation = self._get_following_number(line, 'rotation=')
self.parse_confidence = self._get_following_array_of_numbers(line, 'confidence=')
self.parse_sample_latency = self._get_following_number(line, 'timeDeltaMS=')
for i in range(0, 4):
if self.parse_confidence is not None:
self._append(self.confidence[i][0], timeindex, i)
self._append(self.confidence[i][1], timeindex, i + self.parse_confidence[i])
else:
self._append(self.confidence[i][0], timeindex, None)
self._append(self.confidence[i][1], timeindex, None)
self._append(self.raw_acceleration_x, timeindex, self.parse_raw_acceleration_x)
self._append(self.raw_acceleration_y, timeindex, self.parse_raw_acceleration_y)
self._append(self.raw_acceleration_z, timeindex, self.parse_raw_acceleration_z)
self._append(self.filtered_acceleration_x, timeindex, self.parse_filtered_acceleration_x)
self._append(self.filtered_acceleration_y, timeindex, self.parse_filtered_acceleration_y)
self._append(self.filtered_acceleration_z, timeindex, self.parse_filtered_acceleration_z)
self._append(self.magnitude, timeindex, self.parse_magnitude)
self._append(self.tilt_angle, timeindex, self.parse_tilt_angle)
self._append(self.orientation_angle, timeindex, self.parse_orientation_angle)
self._append(self.actual_orientation, timeindex, self.parse_actual_orientation)
self._append(self.proposed_orientation, timeindex, self.parse_proposed_orientation)
self._append(self.combined_confidence, timeindex, self.parse_combined_confidence)
self._append(self.orientation_confidence, timeindex, self.parse_orientation_confidence)
self._append(self.tilt_confidence, timeindex, self.parse_tilt_confidence)
self._append(self.magnitude_confidence, timeindex, self.parse_magnitude_confidence)
self._append(self.sample_latency, timeindex, self.parse_sample_latency)
self._reset_parse_state()
# Scroll the plots.
if timeindex > timespan:
bottom = int(timeindex) - timespan + scrolljump
self.timebase += timedelta(seconds=bottom)
self._scroll(self.raw_acceleration_x, bottom)
self._scroll(self.raw_acceleration_y, bottom)
self._scroll(self.raw_acceleration_z, bottom)
self._scroll(self.filtered_acceleration_x, bottom)
self._scroll(self.filtered_acceleration_y, bottom)
self._scroll(self.filtered_acceleration_z, bottom)
self._scroll(self.magnitude, bottom)
self._scroll(self.tilt_angle, bottom)
self._scroll(self.orientation_angle, bottom)
self._scroll(self.actual_orientation, bottom)
self._scroll(self.proposed_orientation, bottom)
self._scroll(self.combined_confidence, bottom)
self._scroll(self.orientation_confidence, bottom)
self._scroll(self.tilt_confidence, bottom)
self._scroll(self.magnitude_confidence, bottom)
self._scroll(self.sample_latency, bottom)
for i in range(0, 4):
self._scroll(self.confidence[i][0], bottom)
self._scroll(self.confidence[i][1], bottom)
# Redraw the plots.
self.raw_acceleration_line_x.set_data(self.raw_acceleration_x)
self.raw_acceleration_line_y.set_data(self.raw_acceleration_y)
self.raw_acceleration_line_z.set_data(self.raw_acceleration_z)
self.filtered_acceleration_line_x.set_data(self.filtered_acceleration_x)
self.filtered_acceleration_line_y.set_data(self.filtered_acceleration_y)
self.filtered_acceleration_line_z.set_data(self.filtered_acceleration_z)
self.magnitude_line.set_data(self.magnitude)
self.tilt_angle_line.set_data(self.tilt_angle)
self.orientation_angle_line.set_data(self.orientation_angle)
self.actual_orientation_line.set_data(self.actual_orientation)
self.proposed_orientation_line.set_data(self.proposed_orientation)
self.combined_confidence_line.set_data(self.combined_confidence)
self.orientation_confidence_line.set_data(self.orientation_confidence)
self.tilt_confidence_line.set_data(self.tilt_confidence)
self.magnitude_confidence_line.set_data(self.magnitude_confidence)
self.sample_latency_line.set_data(self.sample_latency)
for poly in self.confidence_polys:
poly.remove()
self.confidence_polys = []
for i in range(0, 4):
self.confidence_polys.append(self.orientation_axes.fill_between(self.confidence[i][0][0],
self.confidence[i][0][1], self.confidence[i][1][1],
facecolor='goldenrod', edgecolor='goldenrod'))
self.fig.canvas.draw_idle()
# Scroll a time series.
def _scroll(self, timeseries, bottom):
bottom_index = bisect.bisect_left(timeseries[0], bottom)
del timeseries[0][:bottom_index]
del timeseries[1][:bottom_index]
for i, timeindex in enumerate(timeseries[0]):
timeseries[0][i] = timeindex - bottom
# Extract a word following the specified prefix.
def _get_following_word(self, line, prefix):
prefix_index = line.find(prefix)
if prefix_index == -1:
return None
start_index = prefix_index + len(prefix)
delim_index = line.find(',', start_index)
if delim_index == -1:
return line[start_index:]
else:
return line[start_index:delim_index]
# Extract a number following the specified prefix.
def _get_following_number(self, line, prefix):
word = self._get_following_word(line, prefix)
if word is None:
return None
return float(word)
# Extract an array of numbers following the specified prefix.
def _get_following_array_of_numbers(self, line, prefix):
prefix_index = line.find(prefix + '[')
if prefix_index == -1:
return None
start_index = prefix_index + len(prefix) + 1
delim_index = line.find(']', start_index)
if delim_index == -1:
return None
result = []
while start_index < delim_index:
comma_index = line.find(', ', start_index, delim_index)
if comma_index == -1:
result.append(float(line[start_index:delim_index]))
break;
result.append(float(line[start_index:comma_index]))
start_index = comma_index + 2
return result
# Add a value to a time series.
def _append(self, timeseries, timeindex, number):
timeseries[0].append(timeindex)
timeseries[1].append(number)
# Parse the logcat timestamp.
# Timestamp has the form '01-21 20:42:42.930'
def _parse_timestamp(self, line):
return datetime.strptime(line[0:18], '%m-%d %H:%M:%S.%f')
# Notice
print "Window Orientation Listener plotting tool"
print "-----------------------------------------\n"
print "Please turn on the Window Orientation Listener logging in Development Settings."
# Start adb.
print "Starting adb logcat.\n"
adb = subprocess.Popen(['adb', 'logcat', '-s', '-v', 'time', 'WindowOrientationListener:V'],
stdout=subprocess.PIPE)
adbout = NonBlockingStream(adb.stdout)
# Prepare plotter.
plotter = Plotter(adbout)
plotter.update()
# Main loop.
plot.show()