My app is locked into portrait orientation only, however in one fragment I have a camera preview where I would like to rotate captured images based on the device orientation. I believe that because my app is portrait only, the following code always logs zero.
Display display = ((WindowManager)getActivity().getSystemService(Context.WINDOW_SERVICE)).getDefaultDisplay();
int rotation = display.getRotation();
Log.i(TAG, "Rotation: " + rotation );
Is it possible to get the actual orientation of the device while locking the app to portrait?
I am targeting android 4.0+ so I'm not concerned if the solution won't work on older devices.
you could implement a SensorEventListener, then look at the Roll in onSensorChanged:
#Override
public void onSensorChanged(SensorEvent event) {
synchronized(this)
{
if (event.sensor.getType() == Sensor.TYPE_ACCELEROMETER) {
mAccelerometerValues = event.values;
}
if (event.sensor.getType() == Sensor.TYPE_MAGNETIC_FIELD) {
mGeomageneticValues = event.values;
}
if ((mAccelerometerValues != null) && (mGeomageneticValues != null))
{
boolean success = SensorManager.getRotationMatrix(R, I, mAccelerometerValues, mGeomageneticValues);
if (success)
{
SensorManager.remapCoordinateSystem(R, SensorManager.AXIS_X, SensorManager.AXIS_Z, outR);
SensorManager.getOrientation(outR,orientation);
mYaw = orientation[0] * MathX.toDegreesF;
mPitch = orientation[1] * MathX.toDegreesF;
mRoll = orientation[2] * MathX.toDegreesF;
String sText = String.format("a:%1.4f\np:%1.4f\nr:%1.4f", yaw,pitch,roll);
}
}
}
}
Related
For an application I'm making, I need to have a camera and a compass. The application is set to be at landscape mode in the manifest.
First I've implemented the compass. As suggested in Android Developers, I used two sensors - Accelerometer and Magnetic Field. This is how I've done it:
I have my activity implement SensorEventListener. In onCreate() I initialize my sensorManager using:
sManager = (SensorManager) getSystemService(SENSOR_SERVICE);
I register my listeners in onResume() like so:
sManager.registerListener(this, sManager.getDefaultSensor(Sensor.TYPE_ACCELEROMETER),SensorManager.SENSOR_DELAY_NORMAL);
sManager.registerListener(this, sManager.getDefaultSensor(Sensor.TYPE_MAGNETIC_FIELD),SensorManager.SENSOR_DELAY_NORMAL);
and of course unregister them in onPause().
I don't use onAccuracyChanged(). this is what I do in onSensorChanged():
#Override
public void onSensorChanged(SensorEvent event) {
switch (event.sensor.getType()) {
case Sensor.TYPE_MAGNETIC_FIELD:
mags = event.values.clone();
break;
case Sensor.TYPE_ACCELEROMETER:
accels = event.values.clone();
break;
}
if (mags != null && accels != null) {
gravity = new float[9];
magnetic = new float[9];
SensorManager.getRotationMatrix(gravity, magnetic, accels, mags);
float[] outGravity = new float[9];
float inclination = (float) Math.acos(gravity[8]);
if (inclination < Math.toRadians(25)
|| inclination > Math.toRadians(155)) {
// device is close to flat. Remap for landscape.
SensorManager.remapCoordinateSystem(gravity, SensorManager.AXIS_Y,SensorManager.AXIS_MINUS_X, outGravity);
SensorManager.getOrientation(outGravity, values);
} else {
// device is not flat. Remap for landscape and perpendicular
SensorManager.remapCoordinateSystem(gravity, SensorManager.AXIS_X,SensorManager.AXIS_Z, outGravity);
SensorManager.getOrientation(outGravity, values);
}
azimuth = Math.round(Math.toDegrees(values[0]));
}
}
As you can see, I differentiate between when the phone is lying flat on the table, and when the user holds it (as you would when taking a picture). When I use this code alone, everything works great more or less. I'm getting correct azimuth values both when phone is lying on the table and when holding it perpendicular to the table (about 5-10 degrees difference, but I can live with that).
The problem starts when adding the camera preview to the application.
I have my activity implement SurfaceHolder.Callback. I initialize my camera in onCreate():
SurfaceView cameraView = (SurfaceView)findViewById(R.id.camera_view);
surfaceHolder = cameraView.getHolder();
surfaceHolder.addCallback(this);
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.HONEYCOMB) {
surfaceHolder.setType(SurfaceHolder.SURFACE_TYPE_PUSH_BUFFERS);
}
This is how I implement the interface:
#Override
public void surfaceCreated(SurfaceHolder surfaceHolder) {
camera = Camera.open();
camera.setDisplayOrientation(0);
}
#Override
public void surfaceChanged(SurfaceHolder surfaceHolder, int i, int i1, int i2) {
if (isCameraOn) {
camera.stopPreview();
isCameraOn = false;
}
if (camera != null) {
try {
camera.setPreviewDisplay(surfaceHolder);
camera.startPreview();
isCameraOn = true;
} catch (IOException e) {
e.printStackTrace();
}
}
}
#Override
public void surfaceDestroyed(SurfaceHolder surfaceHolder) {
camera.stopPreview();
camera.release();
camera = null;
}
When I add the camera code to my project, and show the camera on the phone's screen, my sensors dont work properly when phone is perpendicular suddenly. If phone is lying flat on the table, the azimuth values I'm getting are correct. When phone is being held perpendicular to the table, my azimuth values are off by about 40 degrees (though stable).
I've tried looking for a solution (both by myself and online), but so far my efforts were in vain. I would love to get some direction on how to tackle this problem.Thanks!
First TYPE_MAGNETIC_FIELD sensor will not available in all devices.
You can use TYPE_ACCELEROMETER sensor alone to accomplish your requirement.
Retrieve accelerometer sensor
Sensor accelerometer = mSensorManager.getDefaultSensor(Sensor.TYPE_ACCELEROMETER);
Just compare and copy values when sensor change event call
#Override
public void onSensorChanged(SensorEvent event) {
if (event.sensor.getType() == Sensor.TYPE_ACCELEROMETER)
mGravity = event.values;
}
Then you can use below function to get sensor values of all axis.
public int[] getDeviceAngles() {
float[] g = mGravity.clone();
double normOfG = Math.sqrt(g[0] * g[0] + g[1] * g[1] + g[2] * g[2]);
// Normalize the accelerometer vector
g[0] = (float) (g[0] / normOfG);
g[1] = (float) (g[1] / normOfG);
g[2] = (float) (g[2] / normOfG);
int x = (int) Math.round(Math.toDegrees(Math.atan2(g[1], g[0])));
int pitch = (int) Math.round(Math.toDegrees(Math.atan2(g[1], g[2])));
int rollValue = (int) Math.round(Math.toDegrees(Math.atan2(g[2], g[0])));
int pitchValue = pitch * -1;
int[] values = new int[3];
values[0] = x;
values[1] = pitchValue;
values[2] = rollValue;
//values contains: azimut, pitch and roll
return values;
}
I'm trying to set map bearing according to device orientation. So far it's working fine, but occasionally the bearing gets set to 0. I have checked by logging my bearing calculation output and the bearing I get through OnCameraChangeListener, it seems my calculation is correct but somehow it resets to 0 inside moveCamera() method. The funny thing is it seems to be occurring at a regular interval (around 5-6 seconds). Has anyone else encountered this issue? Is there a reason for this? Is there a workaround? Thanks in advance.
EDIT
Here is the code I used for rotating the map.
private void init() {
mSensorManager.registerListener(this, mAccelerometer, SensorManager.SENSOR_DELAY_NORMAL);
mSensorManager.registerListener(this, mMagnetometer, SensorManager.SENSOR_DELAY_NORMAL);
}
#Override
public void onSensorChanged(SensorEvent event) {
if (event.sensor.getType() == Sensor.TYPE_ACCELEROMETER) {
mGravity = event.values;
} else if (event.sensor.getType() == Sensor.TYPE_MAGNETIC_FIELD) {
mGeomagnetic = event.values;
}
if(mGravity != null && mGeomagnetic != null) {
float R[] = new float[9];
float I[] = new float[9];
float newBearing;
boolean success = SensorManager.getRotationMatrix(R, I, mGravity, mGeomagnetic);
if (success) {
float orientation[] = new float[3];
SensorManager.getOrientation(R, orientation);
if (mBearing != Float.MIN_VALUE) {
newBearing = mBearing
+ ALPHA
* ((float) Math.toDegrees(orientation[0]) - mBearing);
} else {
newBearing = ((float) Math.toDegrees(orientation[0]));
}
mBearing = newBearing;
CameraPosition cameraPosition = CameraPosition
.builder(mMap.getCameraPosition())
.bearing(mBearing).build();
CameraUpdate cameraUpdate = CameraUpdateFactory
.newCameraPosition(cameraPosition);
mMap.moveCamera(cameraUpdate);
}
}
}
When the phone is rotated, Android will destroy the activity and rebuild it losing any ephemeral state that you haven't saved and restored. If you wish to keep android from doing that you can tell it to not rebuild your activity on configuration changes.
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
...
<application
...
<activity
android:name="<YourActivityName>"
android:configChanges="orientation|keyboard|keyboardHidden|screenSize"
...
/>
/>
/>
There are a number of events that will cause a configuration change and a rebuild of your activity. That's why keyboard and screenSize are included in that list.
I am trying to adapt a low pass filter from this site:
Low Pass Filter
My problem is that I do not know what is the goal of below line:
int rotation = Compatibility.getRotation(this);
I have googled a lot in order to search information about this but without no luck. Anyone knows what it does?
See below piece of code:
#Override
public void onSensorChanged(SensorEvent evt) {
if (evt.sensor.getType() == Sensor.TYPE_ACCELEROMETER) {
gravSensorVals = lowPass(evt.values.clone(), gravSensorVals);
} else if (evt.sensor.getType() == Sensor.TYPE_MAGNETIC_FIELD) {
magSensorVals = lowPass(evt.values.clone(), magSensorVals);
}
if (gravSensorVals != null && magSensorVals != null) {
SensorManager.getRotationMatrix(RTmp, I, gravSensorVals, magSensorVals);
int rotation = Compatibility.getRotation(this);
if (rotation == 1) {
SensorManager.remapCoordinateSystem(RTmp, SensorManager.AXIS_X, SensorManager.AXIS_MINUS_Z, Rot);
} else {
SensorManager.remapCoordinateSystem(RTmp, SensorManager.AXIS_Y, SensorManager.AXIS_MINUS_Z, Rot);
}
SensorManager.getOrientation(Rot, results);
UIARView.azimuth = (float)(((results[0]*180)/Math.PI)+180);
UIARView.pitch = (float)(((results[1]*180/Math.PI))+90);
UIARView.roll = (float)(((results[2]*180/Math.PI)));
radarMarkerView.postInvalidate();
}
}
From android documentation: here, it will check the device orientation. So value of one is ROTATION_90. device rotated 90 degrees counter clockwise.
In my 2d game I have the following code which is responsible for game entity control (flying plane). It all seems to be working fine when it comes for phones, but unfortunately I've getting some information that on Android tablets the steering is completely unreliable (axis are messed up, or it doesn't work at all). Unfortunately I don't have a tablet of my own, so I cannot investigate it closer. So.. what's wrong with the following code? (for the clarity I put only the code related to sensors)
// ...
private float[] accelerometerValues;
private float[] magneticFieldValues;
private float[] R;
private float[] I;
private float[] outR;
private float[] sensorValues;
private Sensor accelerometer;
private Sensor magneticField;
// ...
// ... sensor initialization
sensorManager = (SensorManager)activity.getSystemService(Context.SENSOR_SERVICE);
if(sensorManager == null)
return;
accelerometer = sensorManager.getDefaultSensor(Sensor.TYPE_ACCELEROMETER);
magneticField = sensorManager.getDefaultSensor(Sensor.TYPE_MAGNETIC_FIELD);
// ...
// ... onPause() sensor is being unregistered
public void onResume() {
if(!sensorManager.registerListener(sensorListener, accelerometer, SensorManager.SENSOR_DELAY_GAME) ||
!sensorManager.registerListener(sensorListener, magneticField, SensorManager.SENSOR_DELAY_GAME))
// ...
// ...
sensorListener = new SensorEventListener() {
public void onAccuracyChanged(Sensor arg0, int arg1) {
}
public void onSensorChanged(android.hardware.SensorEvent event) {
synchronized(InputMgr.this) {
switch(event.sensor.getType()) {
case Sensor.TYPE_ACCELEROMETER:
System.arraycopy(event.values, 0, accelerometerValues, 0, 3);
break;
case Sensor.TYPE_MAGNETIC_FIELD:
System.arraycopy(event.values, 0, magneticFieldValues, 0, 3);
break;
}
}
}
};
// ...
// used somewhere in the game
public void getSensorValues(float values[]) {
synchronized(InputMgr.this) {
SensorManager.getRotationMatrix(R, I, accelerometerValues, magneticFieldValues);
SensorManager.remapCoordinateSystem(R, SensorManager.AXIS_Y, SensorManager.AXIS_X, outR);
SensorManager.getOrientation(outR, sensorValues);
System.arraycopy(sensorValues, 0, values, 0, sensorValues.length);
}
}
This wont cheer you up much but I have a similar problem. I can see a TYPE_MAGNETIC_FIELD sensor, I can add a an event listener to it but I never get any data from it. Other sensors work fine. This is on a Galaxy Tab 7.
onSensorChanged(SensorEvent event) never fires a case Sensor.TYPE_MAGNETIC_FIELD:
As such I get no data from the Magnetic Field Sensor.
if you solve it let us know =)
On devices whose default orientation is landscape (-> most tablets), the sensor values are kind of 'wrong' (I don't know why). So you need to catch those devices and remap your Rotation Matrix.
To check whether the matrix needs to be remapped, you can use this code:
public boolean needToRemapOrientationMatrix;
// compute once (e.g. in onCreate() of your Activity):
Display display = ((WindowManager)getSystemService(Context.WINDOW_SERVICE)).getDefaultDisplay();
int orientation;
if(display.getWidth() < display.getHeight()) orientation = Configuration.ORIENTATION_PORTRAIT;
else if(display.getWidth() > display.getHeight()) orientation = Configuration.ORIENTATION_LANDSCAPE;
else orientation = Configuration.ORIENTATION_SQUARE;
int rotation = display.getRotation();
needToRemapOrientationMatrix =
(orientation==Configuration.ORIENTATION_LANDSCAPE && (rotation==Surface.ROTATION_0 || rotation==Surface.ROTATION_180)) ||
(orientation==Configuration.ORIENTATION_PORTRAIT && (rotation==Surface.ROTATION_90 || rotation==Surface.ROTATION_270));
And when you read the sensor values, remap the matrix if needed:
public void getSensorValues(float values[]) {
synchronized(InputMgr.this) {
SensorManager.getRotationMatrix(R, I, accelerometerValues, magneticFieldValues);
if(needToRemapOrientationMatrix)
SensorManager.remapCoordinateSystem(R, SensorManager.AXIS_MINUS_Y, SensorManager.AXIS_X, R);
SensorManager.remapCoordinateSystem(R, SensorManager.AXIS_Y, SensorManager.AXIS_X, outR);
SensorManager.getOrientation(outR, sensorValues);
System.arraycopy(sensorValues, 0, values, 0, sensorValues.length);
}
}
This worked for me, I hope it helps.
I'm currently experimenting with some sensors of Android phones. For testing, I'm using a Samsung Galaxy S. As it does not have a gyroscope, I'm using accelerometer and sensor for magnetic field.
What I basically want to do, is to get a certain angle, when moving the device. I try to explain: consider you are holding the phone in landscape mode in front of your face and then you turn yourself by 90 degrees to the right.
I use the following code to get the current rotation matrix:
#Override
public void onSensorChanged(SensorEvent event) {
if (event.sensor.getType() == Sensor.TYPE_ACCELEROMETER) {
mGravity = event.values.clone();
}
if (event.sensor.getType() == Sensor.TYPE_MAGNETIC_FIELD) {
mGeomagnetic = event.values.clone();
}
if (mGravity != null && mGeomagnetic != null) {
float R[] = new float[9];
float I[] = new float[9];
boolean success = SensorManager.getRotationMatrix(R, I, mGravity,
mGeomagnetic);
}
}
This works well and then I use SensorManager.getAngleChange(angleChange, R, lastR); to achieve the angle change.
I then get (roughly) 65° in angleChange[1] if I turn myself as described above and do not tilt the phone or change anything else...
But if I also tilt the phone by 90° when turning myself (so that display is looking to the ceiling afterwards) I get (roughly) 90° in angleChange[1].
I'm very confused now, why such a rotation affects the value in angleChange[1] and on the other hand why it is needed to get the expected 90°.
What I want to achieve is to get the angle when moving the phone as described above (not in 90° degree steps but this sort of orientation change) no matter which other orientation changes (along the two other axes) are made.
Is there any possibility for this?