Fix orientation when activity handles rotations (#1117)

pull/1164/head
Mattia Iavarone 3 years ago committed by GitHub
parent c2e02923de
commit 399844321b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 10
      cameraview/src/main/java/com/otaliastudios/cameraview/CameraView.java
  2. 39
      cameraview/src/main/java/com/otaliastudios/cameraview/internal/OrientationHelper.java
  3. 3
      cameraview/src/main/java/com/otaliastudios/cameraview/preview/SurfaceCameraPreview.java

@ -2370,12 +2370,10 @@ public class CameraView extends FrameLayout implements LifecycleObserver {
} }
@Override @Override
public void onDisplayOffsetChanged(int displayOffset, boolean willRecreate) { public void onDisplayOffsetChanged() {
LOG.i("onDisplayOffsetChanged", displayOffset, "recreate:", willRecreate); if (isOpened()) {
if (isOpened() && !willRecreate) { // We can't handle display offset (View angle) changes without restarting.
// Display offset changes when the device rotation lock is off and the activity // See comments in OrientationHelper for more information.
// 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.
LOG.w("onDisplayOffsetChanged", "restarting the camera."); LOG.w("onDisplayOffsetChanged", "restarting the camera.");
close(); close();
open(); open();

@ -1,6 +1,8 @@
package com.otaliastudios.cameraview.internal; package com.otaliastudios.cameraview.internal;
import android.app.Activity;
import android.content.Context; import android.content.Context;
import android.content.res.Configuration;
import android.hardware.SensorManager; import android.hardware.SensorManager;
import androidx.annotation.NonNull; import androidx.annotation.NonNull;
import androidx.annotation.VisibleForTesting; import androidx.annotation.VisibleForTesting;
@ -9,6 +11,7 @@ import android.hardware.display.DisplayManager;
import android.os.Build; import android.os.Build;
import android.os.Handler; import android.os.Handler;
import android.os.Looper; import android.os.Looper;
import android.view.View;
import android.view.Display; import android.view.Display;
import android.view.OrientationEventListener; import android.view.OrientationEventListener;
import android.view.Surface; import android.view.Surface;
@ -18,6 +21,36 @@ 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 wrt the device default * and the display offset (which depends on the activity orientation wrt the device default
* orientation). * orientation).
*
* Note: any change in the display offset should restart the camera engine, because it reads
* from the angles container at startup and computes size based on that. This is tricky because
* activity behavior can differ:
*
* - if activity is locked to some orientation, {@link #mDisplayOffset} won't change, and
* {@link View#onConfigurationChanged(Configuration)} won't be called.
* The library will work fine.
*
* - if the activity is unlocked and does NOT handle orientation changes with android:configChanges,
* the actual behavior differs depending on the rotation.
* - the configuration callback is never called, of course.
* - for 90°/-90° rotations, the activity is recreated. Sometime you get {@link #mDisplayOffset}
* callback before destruction, sometimes you don't - in any case it's going to recreate.
* - for 180°/-180°, the activity is NOT recreated! But we can rely on {@link #mDisplayOffset}
* changing with a 180 delta and restart the engine.
*
* - lastly, if the activity is unlocked and DOES handle orientation changes with android:configChanges,
* as it will often be the case in a modern Compose app,
* - you always get the {@link #mDisplayOffset} callback
* - for 90°/-90° rotations, the view also gets the configuration changed callback.
* - for 180°/-180°, the view won't get it because configuration only cares about portrait vs. landscape.
*
* In practice, since we don't control the activity and we can't easily inspect the configChanges
* flags at runtime, a good solution is to always restart when the display offset changes. We might
* do useless restarts in one rare scenario (unlocked, no android:configChanges, 90° rotation,
* display offset callback received before destruction) but that's acceptable.
*
* Tried to avoid that by looking at {@link Activity#isChangingConfigurations()}, but it's always
* false by the time the display offset callback is invoked.
*/ */
public class OrientationHelper { public class OrientationHelper {
@ -26,7 +59,7 @@ public class OrientationHelper {
*/ */
public interface Callback { public interface Callback {
void onDeviceOrientationChanged(int deviceOrientation); void onDeviceOrientationChanged(int deviceOrientation);
void onDisplayOffsetChanged(int displayOffset, boolean willRecreate); void onDisplayOffsetChanged();
} }
private final Handler mHandler = new Handler(Looper.getMainLooper()); private final Handler mHandler = new Handler(Looper.getMainLooper());
@ -87,9 +120,7 @@ public class OrientationHelper {
int newDisplayOffset = findDisplayOffset(); int newDisplayOffset = findDisplayOffset();
if (newDisplayOffset != oldDisplayOffset) { if (newDisplayOffset != oldDisplayOffset) {
mDisplayOffset = newDisplayOffset; mDisplayOffset = newDisplayOffset;
// With 180 degrees flips, the activity is not recreated. mCallback.onDisplayOffsetChanged();
boolean willRecreate = Math.abs(newDisplayOffset - oldDisplayOffset) != 180;
mCallback.onDisplayOffsetChanged(newDisplayOffset, willRecreate);
} }
} }
}; };

@ -46,6 +46,7 @@ public class SurfaceCameraPreview extends CameraPreview<SurfaceView, SurfaceHold
public void surfaceCreated(SurfaceHolder holder) { public void surfaceCreated(SurfaceHolder holder) {
// This is too early to call anything. // This is too early to call anything.
// surfaceChanged is guaranteed to be called after, with exact dimensions. // surfaceChanged is guaranteed to be called after, with exact dimensions.
LOG.i("callback: surfaceCreated.");
} }
@Override @Override
@ -64,7 +65,7 @@ public class SurfaceCameraPreview extends CameraPreview<SurfaceView, SurfaceHold
@Override @Override
public void surfaceDestroyed(SurfaceHolder holder) { public void surfaceDestroyed(SurfaceHolder holder) {
LOG.i("callback:", "surfaceDestroyed"); LOG.i("callback: surfaceDestroyed");
dispatchOnSurfaceDestroyed(); dispatchOnSurfaceDestroyed();
mDispatched = false; mDispatched = false;
} }

Loading…
Cancel
Save