OrientationListener.java
/*
* Copyright (C) 2018 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.
*/
package androidx.media3.exoplayer.video.spherical;
import android.hardware.Sensor;
import android.hardware.SensorEvent;
import android.hardware.SensorEventListener;
import android.hardware.SensorManager;
import android.opengl.Matrix;
import android.view.Display;
import android.view.Surface;
import androidx.annotation.BinderThread;
/**
* Listens for orientation sensor events, converts event data to rotation matrix and roll value, and
* notifies its own listeners.
*/
/* package */ final class OrientationListener implements SensorEventListener {
/** A listener for orientation changes. */
public interface Listener {
/**
* Called on device orientation change.
*
* @param deviceOrientationMatrix A 4x4 matrix defining device orientation.
* @param deviceRoll Device roll value, in radians. The range of values is -π/2 to π/2.
*/
void onOrientationChange(float[] deviceOrientationMatrix, float deviceRoll);
}
private final float[] deviceOrientationMatrix4x4 = new float[16];
private final float[] tempMatrix4x4 = new float[16];
private final float[] recenterMatrix4x4 = new float[16];
private final float[] angles = new float[3];
private final Display display;
private final Listener[] listeners;
private boolean recenterMatrixComputed;
public OrientationListener(Display display, Listener... listeners) {
this.display = display;
this.listeners = listeners;
}
@Override
@BinderThread
public void onSensorChanged(SensorEvent event) {
SensorManager.getRotationMatrixFromVector(deviceOrientationMatrix4x4, event.values);
rotateAroundZ(deviceOrientationMatrix4x4, display.getRotation());
float roll = extractRoll(deviceOrientationMatrix4x4);
// Rotation vector sensor assumes Y is parallel to the ground.
rotateYtoSky(deviceOrientationMatrix4x4);
recenter(deviceOrientationMatrix4x4);
notifyListeners(deviceOrientationMatrix4x4, roll);
}
@Override
public void onAccuracyChanged(Sensor sensor, int accuracy) {
// Do nothing.
}
private void notifyListeners(float[] deviceOrientationMatrix, float roll) {
for (Listener listener : listeners) {
listener.onOrientationChange(deviceOrientationMatrix, roll);
}
}
private void recenter(float[] matrix) {
if (!recenterMatrixComputed) {
FrameRotationQueue.computeRecenterMatrix(recenterMatrix4x4, matrix);
recenterMatrixComputed = true;
}
System.arraycopy(matrix, 0, tempMatrix4x4, 0, tempMatrix4x4.length);
Matrix.multiplyMM(matrix, 0, tempMatrix4x4, 0, recenterMatrix4x4, 0);
}
private float extractRoll(float[] matrix) {
// Remapping is required since we need the calculated roll of the phone to be independent of the
// phone's pitch & yaw.
SensorManager.remapCoordinateSystem(
matrix, SensorManager.AXIS_X, SensorManager.AXIS_MINUS_Z, tempMatrix4x4);
SensorManager.getOrientation(tempMatrix4x4, angles);
return angles[2];
}
private void rotateAroundZ(float[] matrix, int rotation) {
int xAxis;
int yAxis;
switch (rotation) {
case Surface.ROTATION_270:
xAxis = SensorManager.AXIS_MINUS_Y;
yAxis = SensorManager.AXIS_X;
break;
case Surface.ROTATION_180:
xAxis = SensorManager.AXIS_MINUS_X;
yAxis = SensorManager.AXIS_MINUS_Y;
break;
case Surface.ROTATION_90:
xAxis = SensorManager.AXIS_Y;
yAxis = SensorManager.AXIS_MINUS_X;
break;
case Surface.ROTATION_0:
return;
default:
throw new IllegalStateException();
}
System.arraycopy(matrix, 0, tempMatrix4x4, 0, tempMatrix4x4.length);
SensorManager.remapCoordinateSystem(tempMatrix4x4, xAxis, yAxis, matrix);
}
private static void rotateYtoSky(float[] matrix) {
Matrix.rotateM(matrix, 0, 90, 1, 0, 0);
}
}