From 399844321bb9b62a48ab24e39e704af8a413e237 Mon Sep 17 00:00:00 2001 From: Mattia Iavarone Date: Wed, 4 Aug 2021 20:45:44 +0200 Subject: [PATCH] Fix orientation when activity handles rotations (#1117) --- .../otaliastudios/cameraview/CameraView.java | 10 ++--- .../internal/OrientationHelper.java | 39 +++++++++++++++++-- .../preview/SurfaceCameraPreview.java | 3 +- 3 files changed, 41 insertions(+), 11 deletions(-) diff --git a/cameraview/src/main/java/com/otaliastudios/cameraview/CameraView.java b/cameraview/src/main/java/com/otaliastudios/cameraview/CameraView.java index dee95caa..f5140eb2 100644 --- a/cameraview/src/main/java/com/otaliastudios/cameraview/CameraView.java +++ b/cameraview/src/main/java/com/otaliastudios/cameraview/CameraView.java @@ -2370,12 +2370,10 @@ public class CameraView extends FrameLayout implements LifecycleObserver { } @Override - public void onDisplayOffsetChanged(int displayOffset, boolean willRecreate) { - LOG.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. + public void onDisplayOffsetChanged() { + if (isOpened()) { + // We can't handle display offset (View angle) changes without restarting. + // See comments in OrientationHelper for more information. LOG.w("onDisplayOffsetChanged", "restarting the camera."); close(); open(); diff --git a/cameraview/src/main/java/com/otaliastudios/cameraview/internal/OrientationHelper.java b/cameraview/src/main/java/com/otaliastudios/cameraview/internal/OrientationHelper.java index 11db5681..d76dc3b1 100644 --- a/cameraview/src/main/java/com/otaliastudios/cameraview/internal/OrientationHelper.java +++ b/cameraview/src/main/java/com/otaliastudios/cameraview/internal/OrientationHelper.java @@ -1,6 +1,8 @@ package com.otaliastudios.cameraview.internal; +import android.app.Activity; import android.content.Context; +import android.content.res.Configuration; import android.hardware.SensorManager; import androidx.annotation.NonNull; import androidx.annotation.VisibleForTesting; @@ -9,6 +11,7 @@ import android.hardware.display.DisplayManager; import android.os.Build; import android.os.Handler; import android.os.Looper; +import android.view.View; import android.view.Display; import android.view.OrientationEventListener; 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) * and the display offset (which depends on the activity orientation wrt the device default * 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 { @@ -26,7 +59,7 @@ public class OrientationHelper { */ public interface Callback { void onDeviceOrientationChanged(int deviceOrientation); - void onDisplayOffsetChanged(int displayOffset, boolean willRecreate); + void onDisplayOffsetChanged(); } private final Handler mHandler = new Handler(Looper.getMainLooper()); @@ -87,9 +120,7 @@ public class OrientationHelper { 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); + mCallback.onDisplayOffsetChanged(); } } }; diff --git a/cameraview/src/main/java/com/otaliastudios/cameraview/preview/SurfaceCameraPreview.java b/cameraview/src/main/java/com/otaliastudios/cameraview/preview/SurfaceCameraPreview.java index ed9073aa..1f625352 100644 --- a/cameraview/src/main/java/com/otaliastudios/cameraview/preview/SurfaceCameraPreview.java +++ b/cameraview/src/main/java/com/otaliastudios/cameraview/preview/SurfaceCameraPreview.java @@ -46,6 +46,7 @@ public class SurfaceCameraPreview extends CameraPreview