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 @Test
public void testEnable() { public void testEnable() {
assertNotNull(helper.mListener); assertEquals(helper.getLastDisplayOffset(), -1);
assertEquals(helper.getDisplayOffset(), -1); assertEquals(helper.getLastDeviceOrientation(), -1);
assertEquals(helper.getDeviceOrientation(), -1);
helper.enable(getContext()); helper.enable();
assertNotNull(helper.mListener); assertNotEquals(helper.getLastDisplayOffset(), -1); // Don't know about device orientation.
assertNotEquals(helper.getDisplayOffset(), -1); // Don't know about device orientation.
// Ensure nothing bad if called twice. // Ensure nothing bad if called twice.
helper.enable(getContext()); helper.enable();
assertNotNull(helper.mListener); assertNotEquals(helper.getLastDisplayOffset(), -1);
assertNotEquals(helper.getDisplayOffset(), -1);
helper.disable(); helper.disable();
assertNotNull(helper.mListener); assertEquals(helper.getLastDisplayOffset(), -1);
assertEquals(helper.getDisplayOffset(), -1); assertEquals(helper.getLastDeviceOrientation(), -1);
assertEquals(helper.getDeviceOrientation(), -1);
} }
@Test @Test
@ -65,37 +61,37 @@ public class OrientationHelperTest extends BaseTest {
// Sometimes (on some APIs) the helper will trigger an update to 0 // 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. // right after enabling. But that's fine for us, times(1) will be OK either way.
helper.enable(getContext()); helper.enable();
helper.mListener.onOrientationChanged(OrientationEventListener.ORIENTATION_UNKNOWN); helper.mDeviceOrientationListener.onOrientationChanged(OrientationEventListener.ORIENTATION_UNKNOWN);
assertEquals(helper.getDeviceOrientation(), 0); assertEquals(helper.getLastDeviceOrientation(), 0);
helper.mListener.onOrientationChanged(10); helper.mDeviceOrientationListener.onOrientationChanged(10);
assertEquals(helper.getDeviceOrientation(), 0); assertEquals(helper.getLastDeviceOrientation(), 0);
helper.mListener.onOrientationChanged(-10); helper.mDeviceOrientationListener.onOrientationChanged(-10);
assertEquals(helper.getDeviceOrientation(), 0); assertEquals(helper.getLastDeviceOrientation(), 0);
helper.mListener.onOrientationChanged(44); helper.mDeviceOrientationListener.onOrientationChanged(44);
assertEquals(helper.getDeviceOrientation(), 0); assertEquals(helper.getLastDeviceOrientation(), 0);
helper.mListener.onOrientationChanged(360); helper.mDeviceOrientationListener.onOrientationChanged(360);
assertEquals(helper.getDeviceOrientation(), 0); assertEquals(helper.getLastDeviceOrientation(), 0);
// Callback called just once. // Callback called just once.
verify(callback, times(1)).onDeviceOrientationChanged(0); verify(callback, times(1)).onDeviceOrientationChanged(0);
helper.mListener.onOrientationChanged(90); helper.mDeviceOrientationListener.onOrientationChanged(90);
helper.mListener.onOrientationChanged(91); helper.mDeviceOrientationListener.onOrientationChanged(91);
assertEquals(helper.getDeviceOrientation(), 90); assertEquals(helper.getLastDeviceOrientation(), 90);
verify(callback, times(1)).onDeviceOrientationChanged(90); verify(callback, times(1)).onDeviceOrientationChanged(90);
helper.mListener.onOrientationChanged(180); helper.mDeviceOrientationListener.onOrientationChanged(180);
assertEquals(helper.getDeviceOrientation(), 180); assertEquals(helper.getLastDeviceOrientation(), 180);
verify(callback, times(1)).onDeviceOrientationChanged(180); verify(callback, times(1)).onDeviceOrientationChanged(180);
helper.mListener.onOrientationChanged(270); helper.mDeviceOrientationListener.onOrientationChanged(270);
assertEquals(helper.getDeviceOrientation(), 270); assertEquals(helper.getLastDeviceOrientation(), 270);
verify(callback, times(1)).onDeviceOrientationChanged(270); verify(callback, times(1)).onDeviceOrientationChanged(270);
// It is still 270 after ORIENTATION_UNKNOWN // It is still 270 after ORIENTATION_UNKNOWN
helper.mListener.onOrientationChanged(OrientationEventListener.ORIENTATION_UNKNOWN); helper.mDeviceOrientationListener.onOrientationChanged(OrientationEventListener.ORIENTATION_UNKNOWN);
assertEquals(helper.getDeviceOrientation(), 270); assertEquals(helper.getLastDeviceOrientation(), 270);
verify(callback, times(1)).onDeviceOrientationChanged(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. // attached. That's why we instantiate the preview here.
doInstantiatePreview(); doInstantiatePreview();
} }
mOrientationHelper.enable(getContext()); mOrientationHelper.enable();
} }
@Override @Override
@ -722,8 +722,8 @@ public class CameraView extends FrameLayout implements LifecycleObserver {
if (mCameraPreview != null) mCameraPreview.onResume(); if (mCameraPreview != null) mCameraPreview.onResume();
if (checkPermissions(getAudio())) { if (checkPermissions(getAudio())) {
// Update display orientation for current CameraEngine // Update display orientation for current CameraEngine
mOrientationHelper.enable(getContext()); mOrientationHelper.enable();
mCameraEngine.getAngles().setDisplayOffset(mOrientationHelper.getDisplayOffset()); mCameraEngine.getAngles().setDisplayOffset(mOrientationHelper.getLastDisplayOffset());
mCameraEngine.start(); mCameraEngine.start();
} }
} }
@ -2073,7 +2073,7 @@ public class CameraView extends FrameLayout implements LifecycleObserver {
@Override @Override
public void onDeviceOrientationChanged(int deviceOrientation) { public void onDeviceOrientationChanged(int deviceOrientation) {
mLogger.i("onDeviceOrientationChanged", deviceOrientation); mLogger.i("onDeviceOrientationChanged", deviceOrientation);
int displayOffset = mOrientationHelper.getDisplayOffset(); int displayOffset = mOrientationHelper.getLastDisplayOffset();
if (!mUseDeviceOrientation) { if (!mUseDeviceOrientation) {
// To fool the engine to return outputs in the VIEW reference system, // To fool the engine to return outputs in the VIEW reference system,
// The device orientation should be set to -displayOffset. // 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 @Override
public void dispatchOnZoomChanged(final float newValue, @Nullable final PointF[] fingers) { public void dispatchOnZoomChanged(final float newValue, @Nullable final PointF[] fingers) {
mLogger.i("dispatchOnZoomChanged", newValue); mLogger.i("dispatchOnZoomChanged", newValue);

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

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