Listen to suspicious display rotation events. Fixes #623

pull/651/head
Mattia Iavarone 6 years ago
parent c816f569f4
commit 03801dab28
  1. 60
      cameraview/src/androidTest/java/com/otaliastudios/cameraview/internal/utils/OrientationHelperTest.java
  2. 21
      cameraview/src/main/java/com/otaliastudios/cameraview/CameraView.java
  3. 2
      cameraview/src/main/java/com/otaliastudios/cameraview/engine/LogAction.java
  4. 100
      cameraview/src/main/java/com/otaliastudios/cameraview/internal/utils/OrientationHelper.java

@ -41,23 +41,19 @@ public class OrientationHelperTest extends BaseTest {
@Test
public void testEnable() {
assertNotNull(helper.mListener);
assertEquals(helper.getDisplayOffset(), -1);
assertEquals(helper.getDeviceOrientation(), -1);
assertEquals(helper.getLastDisplayOffset(), -1);
assertEquals(helper.getLastDeviceOrientation(), -1);
helper.enable(getContext());
assertNotNull(helper.mListener);
assertNotEquals(helper.getDisplayOffset(), -1); // Don't know about device orientation.
helper.enable();
assertNotEquals(helper.getLastDisplayOffset(), -1); // Don't know about device orientation.
// Ensure nothing bad if called twice.
helper.enable(getContext());
assertNotNull(helper.mListener);
assertNotEquals(helper.getDisplayOffset(), -1);
helper.enable();
assertNotEquals(helper.getLastDisplayOffset(), -1);
helper.disable();
assertNotNull(helper.mListener);
assertEquals(helper.getDisplayOffset(), -1);
assertEquals(helper.getDeviceOrientation(), -1);
assertEquals(helper.getLastDisplayOffset(), -1);
assertEquals(helper.getLastDeviceOrientation(), -1);
}
@Test
@ -65,37 +61,37 @@ public class OrientationHelperTest extends BaseTest {
// Sometimes (on some APIs) the helper will trigger an update to 0
// right after enabling. But that's fine for us, times(1) will be OK either way.
helper.enable(getContext());
helper.mListener.onOrientationChanged(OrientationEventListener.ORIENTATION_UNKNOWN);
assertEquals(helper.getDeviceOrientation(), 0);
helper.mListener.onOrientationChanged(10);
assertEquals(helper.getDeviceOrientation(), 0);
helper.mListener.onOrientationChanged(-10);
assertEquals(helper.getDeviceOrientation(), 0);
helper.mListener.onOrientationChanged(44);
assertEquals(helper.getDeviceOrientation(), 0);
helper.mListener.onOrientationChanged(360);
assertEquals(helper.getDeviceOrientation(), 0);
helper.enable();
helper.mDeviceOrientationListener.onOrientationChanged(OrientationEventListener.ORIENTATION_UNKNOWN);
assertEquals(helper.getLastDeviceOrientation(), 0);
helper.mDeviceOrientationListener.onOrientationChanged(10);
assertEquals(helper.getLastDeviceOrientation(), 0);
helper.mDeviceOrientationListener.onOrientationChanged(-10);
assertEquals(helper.getLastDeviceOrientation(), 0);
helper.mDeviceOrientationListener.onOrientationChanged(44);
assertEquals(helper.getLastDeviceOrientation(), 0);
helper.mDeviceOrientationListener.onOrientationChanged(360);
assertEquals(helper.getLastDeviceOrientation(), 0);
// Callback called just once.
verify(callback, times(1)).onDeviceOrientationChanged(0);
helper.mListener.onOrientationChanged(90);
helper.mListener.onOrientationChanged(91);
assertEquals(helper.getDeviceOrientation(), 90);
helper.mDeviceOrientationListener.onOrientationChanged(90);
helper.mDeviceOrientationListener.onOrientationChanged(91);
assertEquals(helper.getLastDeviceOrientation(), 90);
verify(callback, times(1)).onDeviceOrientationChanged(90);
helper.mListener.onOrientationChanged(180);
assertEquals(helper.getDeviceOrientation(), 180);
helper.mDeviceOrientationListener.onOrientationChanged(180);
assertEquals(helper.getLastDeviceOrientation(), 180);
verify(callback, times(1)).onDeviceOrientationChanged(180);
helper.mListener.onOrientationChanged(270);
assertEquals(helper.getDeviceOrientation(), 270);
helper.mDeviceOrientationListener.onOrientationChanged(270);
assertEquals(helper.getLastDeviceOrientation(), 270);
verify(callback, times(1)).onDeviceOrientationChanged(270);
// It is still 270 after ORIENTATION_UNKNOWN
helper.mListener.onOrientationChanged(OrientationEventListener.ORIENTATION_UNKNOWN);
assertEquals(helper.getDeviceOrientation(), 270);
helper.mDeviceOrientationListener.onOrientationChanged(OrientationEventListener.ORIENTATION_UNKNOWN);
assertEquals(helper.getLastDeviceOrientation(), 270);
verify(callback, times(1)).onDeviceOrientationChanged(270);
}
}

@ -356,7 +356,7 @@ public class CameraView extends FrameLayout implements LifecycleObserver {
// attached. That's why we instantiate the preview here.
doInstantiatePreview();
}
mOrientationHelper.enable(getContext());
mOrientationHelper.enable();
}
@Override
@ -722,8 +722,8 @@ public class CameraView extends FrameLayout implements LifecycleObserver {
if (mCameraPreview != null) mCameraPreview.onResume();
if (checkPermissions(getAudio())) {
// Update display orientation for current CameraEngine
mOrientationHelper.enable(getContext());
mCameraEngine.getAngles().setDisplayOffset(mOrientationHelper.getDisplayOffset());
mOrientationHelper.enable();
mCameraEngine.getAngles().setDisplayOffset(mOrientationHelper.getLastDisplayOffset());
mCameraEngine.start();
}
}
@ -2073,7 +2073,7 @@ public class CameraView extends FrameLayout implements LifecycleObserver {
@Override
public void onDeviceOrientationChanged(int deviceOrientation) {
mLogger.i("onDeviceOrientationChanged", deviceOrientation);
int displayOffset = mOrientationHelper.getDisplayOffset();
int displayOffset = mOrientationHelper.getLastDisplayOffset();
if (!mUseDeviceOrientation) {
// To fool the engine to return outputs in the VIEW reference system,
// The device orientation should be set to -displayOffset.
@ -2093,6 +2093,19 @@ public class CameraView extends FrameLayout implements LifecycleObserver {
});
}
@Override
public void onDisplayOffsetChanged(int displayOffset, boolean willRecreate) {
mLogger.i("onDisplayOffsetChanged", displayOffset, "recreate:", willRecreate);
if (isOpened() && !willRecreate) {
// Display offset changes when the device rotation lock is off and the activity
// is free to rotate. However, some changes will NOT recreate the activity, namely
// 180 degrees flips. In this case, we must restart the camera manually.
mLogger.w("onDisplayOffsetChanged", "restarting the camera.");
close();
open();
}
}
@Override
public void dispatchOnZoomChanged(final float newValue, @Nullable final PointF[] fingers) {
mLogger.i("dispatchOnZoomChanged", newValue);

@ -36,7 +36,7 @@ class LogAction extends BaseAction {
" afState: " + afState + " afTriggerState: " + afTriggerState;
if (!log.equals(lastLog)) {
lastLog = log;
LOG.i(log);
LOG.v(log);
}
}

@ -5,6 +5,8 @@ import android.hardware.SensorManager;
import androidx.annotation.NonNull;
import androidx.annotation.VisibleForTesting;
import android.hardware.display.DisplayManager;
import android.os.Build;
import android.view.Display;
import android.view.OrientationEventListener;
import android.view.Surface;
@ -12,21 +14,28 @@ import android.view.WindowManager;
/**
* Helps with keeping track of both device orientation (which changes when device is rotated)
* and the display offset (which depends on the activity orientation
* wrt the device default orientation).
* and the display offset (which depends on the activity orientation wrt the device default
* orientation).
*/
public class OrientationHelper {
/**
* Receives callback about the device orientation changes.
* Receives callback about the orientation changes.
*/
public interface Callback {
void onDeviceOrientationChanged(int deviceOrientation);
void onDisplayOffsetChanged(int displayOffset, boolean willRecreate);
}
@VisibleForTesting final OrientationEventListener mListener;
private final Context mContext;
private final Callback mCallback;
@VisibleForTesting
final OrientationEventListener mDeviceOrientationListener;
private int mDeviceOrientation = -1;
@VisibleForTesting
final DisplayManager.DisplayListener mDisplayOffsetListener;
private int mDisplayOffset = -1;
/**
@ -35,57 +44,78 @@ public class OrientationHelper {
* @param callback a {@link Callback}
*/
public OrientationHelper(@NonNull Context context, @NonNull Callback callback) {
mContext = context;
mCallback = callback;
mListener = new OrientationEventListener(context.getApplicationContext(),
mDeviceOrientationListener = new OrientationEventListener(context.getApplicationContext(),
SensorManager.SENSOR_DELAY_NORMAL) {
@SuppressWarnings("ConstantConditions")
@Override
public void onOrientationChanged(int orientation) {
int or = 0;
int deviceOrientation = 0;
if (orientation == OrientationEventListener.ORIENTATION_UNKNOWN) {
or = mDeviceOrientation != -1 ? mDeviceOrientation : 0;
deviceOrientation = mDeviceOrientation != -1 ? mDeviceOrientation : 0;
} else if (orientation >= 315 || orientation < 45) {
or = 0;
deviceOrientation = 0;
} else if (orientation >= 45 && orientation < 135) {
or = 90;
deviceOrientation = 90;
} else if (orientation >= 135 && orientation < 225) {
or = 180;
deviceOrientation = 180;
} else if (orientation >= 225 && orientation < 315) {
or = 270;
deviceOrientation = 270;
}
if (or != mDeviceOrientation) {
mDeviceOrientation = or;
if (deviceOrientation != mDeviceOrientation) {
mDeviceOrientation = deviceOrientation;
mCallback.onDeviceOrientationChanged(mDeviceOrientation);
}
}
};
if (Build.VERSION.SDK_INT >= 17) {
mDisplayOffsetListener = new DisplayManager.DisplayListener() {
public void onDisplayAdded(int displayId) { }
public void onDisplayRemoved(int displayId) { }
@Override
public void onDisplayChanged(int displayId) {
int oldDisplayOffset = mDisplayOffset;
int newDisplayOffset = findDisplayOffset();
if (newDisplayOffset != oldDisplayOffset) {
mDisplayOffset = newDisplayOffset;
// With 180 degrees flips, the activity is not recreated.
boolean willRecreate = Math.abs(newDisplayOffset - oldDisplayOffset) != 180;
mCallback.onDisplayOffsetChanged(newDisplayOffset, willRecreate);
}
}
};
} else {
mDisplayOffsetListener = null;
}
}
/**
* Enables this listener.
* @param context a context
*/
public void enable(@NonNull Context context) {
Display display = ((WindowManager) context
.getSystemService(Context.WINDOW_SERVICE))
.getDefaultDisplay();
switch (display.getRotation()) {
case Surface.ROTATION_0: mDisplayOffset = 0; break;
case Surface.ROTATION_90: mDisplayOffset = 90; break;
case Surface.ROTATION_180: mDisplayOffset = 180; break;
case Surface.ROTATION_270: mDisplayOffset = 270; break;
default: mDisplayOffset = 0; break;
public void enable() {
mDisplayOffset = findDisplayOffset();
if (Build.VERSION.SDK_INT >= 17) {
DisplayManager manager = (DisplayManager)
mContext.getSystemService(Context.DISPLAY_SERVICE);
manager.registerDisplayListener(mDisplayOffsetListener, null);
}
mListener.enable();
mDeviceOrientationListener.enable();
}
/**
* Disables this listener.
*/
public void disable() {
mListener.disable();
mDeviceOrientationListener.disable();
if (Build.VERSION.SDK_INT >= 17) {
DisplayManager manager = (DisplayManager)
mContext.getSystemService(Context.DISPLAY_SERVICE);
manager.unregisterDisplayListener(mDisplayOffsetListener);
}
mDisplayOffset = -1;
mDeviceOrientation = -1;
}
@ -94,7 +124,8 @@ public class OrientationHelper {
* Returns the current device orientation.
* @return device orientation
*/
public int getDeviceOrientation() {
@SuppressWarnings("WeakerAccess")
public int getLastDeviceOrientation() {
return mDeviceOrientation;
}
@ -102,7 +133,20 @@ public class OrientationHelper {
* Returns the current display offset.
* @return display offset
*/
public int getDisplayOffset() {
public int getLastDisplayOffset() {
return mDisplayOffset;
}
private int findDisplayOffset() {
Display display = ((WindowManager) mContext
.getSystemService(Context.WINDOW_SERVICE))
.getDefaultDisplay();
switch (display.getRotation()) {
case Surface.ROTATION_0: return 0;
case Surface.ROTATION_90: return 90;
case Surface.ROTATION_180: return 180;
case Surface.ROTATION_270: return 270;
default: return 0;
}
}
}

Loading…
Cancel
Save