/* * Copyright (C) 2014 Sean J. Barbeau (sjbarbeau@gmail.com), University of South Florida * * 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. */ package org.onebusaway.android.util; import org.onebusaway.android.app.Application; import android.annotation.TargetApi; import android.content.Context; import android.hardware.Sensor; import android.hardware.SensorEvent; import android.hardware.SensorEventListener; import android.hardware.SensorManager; import android.os.Build; import android.util.Log; import android.view.Surface; import android.view.WindowManager; import java.util.ArrayList; /** * A sensor-based orientation helperclass , which allows listeners to receive orientation updates */ public class OrientationHelper implements SensorEventListener { public interface Listener { /** * Called every time there is an update to the orientation * * @param deltaHeading change in heading from last heading value * @param deltaPitch change in pitch from last pitch value */ void onOrientationChanged(float heading, float pitch, float deltaHeading, float deltaPitch); } static final String TAG = "OrientationHelper"; Context mContext; SensorManager mSensorManager; private float[] mRotationMatrix = new float[16]; private static float[] mRemappedMatrix = new float[16]; private float[] mOrientation = new float[9]; private float[] history = new float[2]; private static float[] mTruncatedRotationVector = new float[4]; private static boolean mTruncateVector = false; private float mHeading; private float mPitch; ArrayList<Listener> mListeners = new ArrayList<Listener>(); public OrientationHelper(Context context) { mContext = context; mSensorManager = (SensorManager) mContext.getSystemService(Context.SENSOR_SERVICE); } public synchronized void registerListener(Listener listener) { if (!mListeners.contains(listener)) { mListeners.add(listener); } // If this is the first listener, make sure we're monitoring the sensors to provide updates if (mListeners.size() == 1) { mSensorManager.registerListener(this, mSensorManager.getDefaultSensor(Sensor.TYPE_ROTATION_VECTOR), SensorManager.SENSOR_DELAY_UI); } } public synchronized void unregisterListener(Listener listener) { if (mListeners.contains(listener)) { mListeners.remove(listener); } if (mListeners.size() == 0) { mSensorManager.unregisterListener(this); } } public synchronized void onResume() { mSensorManager.registerListener(this, mSensorManager.getDefaultSensor(Sensor.TYPE_ROTATION_VECTOR), SensorManager.SENSOR_DELAY_UI); } public synchronized void onPause() { mSensorManager.unregisterListener(this); } @TargetApi(Build.VERSION_CODES.GINGERBREAD) @Override public void onSensorChanged(SensorEvent event) { float xDelta = 0f; float yDelta = 0f; switch (event.sensor.getType()) { case Sensor.TYPE_ROTATION_VECTOR: // Modern rotation vector sensors if (!mTruncateVector) { try { SensorManager.getRotationMatrixFromVector(mRotationMatrix, event.values); } catch (IllegalArgumentException e) { // On some Samsung devices, an exception is thrown if this vector > 4 // See https://github.com/barbeau/gpstest/issues/39 // Truncate the array, since we can deal with only the first four values Log.e(TAG, "Samsung device error? Will truncate vectors - " + e); mTruncateVector = true; // Do the truncation here the first time the exception occurs getRotationMatrixFromTruncatedVector(event.values); } } else { // Truncate the array to avoid the exception on some devices (see #39) getRotationMatrixFromTruncatedVector(event.values); } WindowManager windowManager = (WindowManager) mContext.getSystemService( Context.WINDOW_SERVICE); int rot = windowManager.getDefaultDisplay().getRotation(); switch (rot) { case Surface.ROTATION_0: // No orientation change, use default coordinate system SensorManager.getOrientation(mRotationMatrix, mOrientation); // Log.d(TAG, "Rotation-0"); break; case Surface.ROTATION_90: // Log.d(TAG, "Rotation-90"); SensorManager.remapCoordinateSystem(mRotationMatrix, SensorManager.AXIS_Y, SensorManager.AXIS_MINUS_X, mRemappedMatrix); SensorManager.getOrientation(mRemappedMatrix, mOrientation); break; case Surface.ROTATION_180: // Log.d(TAG, "Rotation-180"); SensorManager .remapCoordinateSystem(mRotationMatrix, SensorManager.AXIS_MINUS_X, SensorManager.AXIS_MINUS_Y, mRemappedMatrix); SensorManager.getOrientation(mRemappedMatrix, mOrientation); break; case Surface.ROTATION_270: // Log.d(TAG, "Rotation-270"); SensorManager .remapCoordinateSystem(mRotationMatrix, SensorManager.AXIS_MINUS_Y, SensorManager.AXIS_X, mRemappedMatrix); SensorManager.getOrientation(mRemappedMatrix, mOrientation); break; default: // This shouldn't happen - assume default orientation SensorManager.getOrientation(mRotationMatrix, mOrientation); // Log.d(TAG, "Rotation-Unknown"); break; } mHeading = (float) Math.toDegrees(mOrientation[0]); mPitch = (float) Math.toDegrees(mOrientation[1]); xDelta = history[0] - mHeading; yDelta = history[1] - mPitch; history[0] = mHeading; history[1] = mPitch; break; case Sensor.TYPE_ORIENTATION: // Legacy orientation sensors mHeading = event.values[0]; xDelta = history[0] - mHeading; break; default: // A sensor we're not using, so return return; } // Use magnetic field to compute true (geographic) north, if data is available Float magneticDeclination = Application.getMagneticDeclination(); if (magneticDeclination != null) { mHeading += magneticDeclination; } // Make sure value is between 0-360 mHeading = MathUtils.mod(mHeading, 360.0f); for (Listener l : mListeners) { l.onOrientationChanged(mHeading, mPitch, xDelta, yDelta); } } @Override public void onAccuracyChanged(Sensor sensor, int accuracy) { } @TargetApi(Build.VERSION_CODES.GINGERBREAD) private void getRotationMatrixFromTruncatedVector(float[] vector) { System.arraycopy(vector, 0, mTruncatedRotationVector, 0, 4); SensorManager.getRotationMatrixFromVector(mRotationMatrix, mTruncatedRotationVector); } }