Skip to content

Commit

Permalink
Fix orientation when activity handles rotations (#1117)
Browse files Browse the repository at this point in the history
  • Loading branch information
natario1 authored Aug 4, 2021
1 parent c2e0292 commit 3998443
Show file tree
Hide file tree
Showing 3 changed files with 41 additions and 11 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -2370,12 +2370,10 @@ public void run() {
}

@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();
Expand Down
Original file line number Diff line number Diff line change
@@ -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;
Expand All @@ -9,6 +11,7 @@
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;
Expand All @@ -18,6 +21,36 @@
* 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 {

Expand All @@ -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());
Expand Down Expand Up @@ -87,9 +120,7 @@ public void onDisplayChanged(int displayId) {
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();
}
}
};
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@ protected SurfaceView onCreateView(@NonNull Context context, @NonNull ViewGroup
public void surfaceCreated(SurfaceHolder holder) {
// This is too early to call anything.
// surfaceChanged is guaranteed to be called after, with exact dimensions.
LOG.i("callback: surfaceCreated.");
}

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

@Override
public void surfaceDestroyed(SurfaceHolder holder) {
LOG.i("callback:", "surfaceDestroyed");
LOG.i("callback: surfaceDestroyed");
dispatchOnSurfaceDestroyed();
mDispatched = false;
}
Expand Down

0 comments on commit 3998443

Please sign in to comment.