Skip to content

Commit 3998443

Browse files
authored
Fix orientation when activity handles rotations (#1117)
1 parent c2e0292 commit 3998443

3 files changed

Lines changed: 41 additions & 11 deletions

File tree

cameraview/src/main/java/com/otaliastudios/cameraview/CameraView.java

Lines changed: 4 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2370,12 +2370,10 @@ public void run() {
23702370
}
23712371

23722372
@Override
2373-
public void onDisplayOffsetChanged(int displayOffset, boolean willRecreate) {
2374-
LOG.i("onDisplayOffsetChanged", displayOffset, "recreate:", willRecreate);
2375-
if (isOpened() && !willRecreate) {
2376-
// Display offset changes when the device rotation lock is off and the activity
2377-
// is free to rotate. However, some changes will NOT recreate the activity, namely
2378-
// 180 degrees flips. In this case, we must restart the camera manually.
2373+
public void onDisplayOffsetChanged() {
2374+
if (isOpened()) {
2375+
// We can't handle display offset (View angle) changes without restarting.
2376+
// See comments in OrientationHelper for more information.
23792377
LOG.w("onDisplayOffsetChanged", "restarting the camera.");
23802378
close();
23812379
open();

cameraview/src/main/java/com/otaliastudios/cameraview/internal/OrientationHelper.java

Lines changed: 35 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
package com.otaliastudios.cameraview.internal;
22

3+
import android.app.Activity;
34
import android.content.Context;
5+
import android.content.res.Configuration;
46
import android.hardware.SensorManager;
57
import androidx.annotation.NonNull;
68
import androidx.annotation.VisibleForTesting;
@@ -9,6 +11,7 @@
911
import android.os.Build;
1012
import android.os.Handler;
1113
import android.os.Looper;
14+
import android.view.View;
1215
import android.view.Display;
1316
import android.view.OrientationEventListener;
1417
import android.view.Surface;
@@ -18,6 +21,36 @@
1821
* Helps with keeping track of both device orientation (which changes when device is rotated)
1922
* and the display offset (which depends on the activity orientation wrt the device default
2023
* orientation).
24+
*
25+
* Note: any change in the display offset should restart the camera engine, because it reads
26+
* from the angles container at startup and computes size based on that. This is tricky because
27+
* activity behavior can differ:
28+
*
29+
* - if activity is locked to some orientation, {@link #mDisplayOffset} won't change, and
30+
* {@link View#onConfigurationChanged(Configuration)} won't be called.
31+
* The library will work fine.
32+
*
33+
* - if the activity is unlocked and does NOT handle orientation changes with android:configChanges,
34+
* the actual behavior differs depending on the rotation.
35+
* - the configuration callback is never called, of course.
36+
* - for 90°/-90° rotations, the activity is recreated. Sometime you get {@link #mDisplayOffset}
37+
* callback before destruction, sometimes you don't - in any case it's going to recreate.
38+
* - for 180°/-180°, the activity is NOT recreated! But we can rely on {@link #mDisplayOffset}
39+
* changing with a 180 delta and restart the engine.
40+
*
41+
* - lastly, if the activity is unlocked and DOES handle orientation changes with android:configChanges,
42+
* as it will often be the case in a modern Compose app,
43+
* - you always get the {@link #mDisplayOffset} callback
44+
* - for 90°/-90° rotations, the view also gets the configuration changed callback.
45+
* - for 180°/-180°, the view won't get it because configuration only cares about portrait vs. landscape.
46+
*
47+
* In practice, since we don't control the activity and we can't easily inspect the configChanges
48+
* flags at runtime, a good solution is to always restart when the display offset changes. We might
49+
* do useless restarts in one rare scenario (unlocked, no android:configChanges, 90° rotation,
50+
* display offset callback received before destruction) but that's acceptable.
51+
*
52+
* Tried to avoid that by looking at {@link Activity#isChangingConfigurations()}, but it's always
53+
* false by the time the display offset callback is invoked.
2154
*/
2255
public class OrientationHelper {
2356

@@ -26,7 +59,7 @@ public class OrientationHelper {
2659
*/
2760
public interface Callback {
2861
void onDeviceOrientationChanged(int deviceOrientation);
29-
void onDisplayOffsetChanged(int displayOffset, boolean willRecreate);
62+
void onDisplayOffsetChanged();
3063
}
3164

3265
private final Handler mHandler = new Handler(Looper.getMainLooper());
@@ -87,9 +120,7 @@ public void onDisplayChanged(int displayId) {
87120
int newDisplayOffset = findDisplayOffset();
88121
if (newDisplayOffset != oldDisplayOffset) {
89122
mDisplayOffset = newDisplayOffset;
90-
// With 180 degrees flips, the activity is not recreated.
91-
boolean willRecreate = Math.abs(newDisplayOffset - oldDisplayOffset) != 180;
92-
mCallback.onDisplayOffsetChanged(newDisplayOffset, willRecreate);
123+
mCallback.onDisplayOffsetChanged();
93124
}
94125
}
95126
};

cameraview/src/main/java/com/otaliastudios/cameraview/preview/SurfaceCameraPreview.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,7 @@ protected SurfaceView onCreateView(@NonNull Context context, @NonNull ViewGroup
4646
public void surfaceCreated(SurfaceHolder holder) {
4747
// This is too early to call anything.
4848
// surfaceChanged is guaranteed to be called after, with exact dimensions.
49+
LOG.i("callback: surfaceCreated.");
4950
}
5051

5152
@Override
@@ -64,7 +65,7 @@ public void surfaceChanged(SurfaceHolder holder, int format, int width, int heig
6465

6566
@Override
6667
public void surfaceDestroyed(SurfaceHolder holder) {
67-
LOG.i("callback:", "surfaceDestroyed");
68+
LOG.i("callback: surfaceDestroyed");
6869
dispatchOnSurfaceDestroyed();
6970
mDispatched = false;
7071
}

0 commit comments

Comments
 (0)