diff --git a/packages/camera/camera_android_camerax/CHANGELOG.md b/packages/camera/camera_android_camerax/CHANGELOG.md index ad47eaa6b88..913a5eeadbb 100644 --- a/packages/camera/camera_android_camerax/CHANGELOG.md +++ b/packages/camera/camera_android_camerax/CHANGELOG.md @@ -1,3 +1,9 @@ +## 0.6.17 + +* Replaces `BroadcastReceiver` usage with an `OrientationEventListener` to detect changes in device + orientation to fix issue where some devices do not report changes in device configuration if it + is rotated between the same sort of orientation (landscape/portrait). + ## 0.6.16 * Fixes incorrect camera preview rotation for landscape-oriented devices. diff --git a/packages/camera/camera_android_camerax/android/src/main/java/io/flutter/plugins/camerax/DeviceOrientationManager.java b/packages/camera/camera_android_camerax/android/src/main/java/io/flutter/plugins/camerax/DeviceOrientationManager.java index bbc0b7d7ca7..382dd2de8fb 100644 --- a/packages/camera/camera_android_camerax/android/src/main/java/io/flutter/plugins/camerax/DeviceOrientationManager.java +++ b/packages/camera/camera_android_camerax/android/src/main/java/io/flutter/plugins/camerax/DeviceOrientationManager.java @@ -10,8 +10,10 @@ import android.content.IntentFilter; import android.content.res.Configuration; import android.view.Display; +import android.view.OrientationEventListener; import android.view.Surface; import androidx.annotation.NonNull; +import androidx.annotation.Nullable; import androidx.annotation.VisibleForTesting; import io.flutter.embedding.engine.systemchannels.PlatformChannel; import io.flutter.embedding.engine.systemchannels.PlatformChannel.DeviceOrientation; @@ -28,6 +30,8 @@ public class DeviceOrientationManager { private PlatformChannel.DeviceOrientation lastOrientation; private BroadcastReceiver broadcastReceiver; + @VisibleForTesting @Nullable protected OrientationEventListener orientationEventListener; + DeviceOrientationManager(DeviceOrientationManagerProxyApi api) { this.api = api; } @@ -38,39 +42,46 @@ Context getContext() { } /** - * Starts listening to the device's sensors or UI for orientation updates. + * Starts listening to the device's sensors for device orientation updates. * *

When orientation information is updated, the callback method of the {@link * DeviceOrientationManagerProxyApi} is called with the new orientation. - * - *

If the device's ACCELEROMETER_ROTATION setting is enabled the {@link - * DeviceOrientationManager} will report orientation updates based on the sensor information. If - * the ACCELEROMETER_ROTATION is disabled the {@link DeviceOrientationManager} will fallback to - * the deliver orientation updates based on the UI orientation. */ public void start() { stop(); - broadcastReceiver = - new BroadcastReceiver() { - @Override - public void onReceive(Context context, Intent intent) { - handleUIOrientationChange(); - } - }; - getContext().registerReceiver(broadcastReceiver, orientationIntentFilter); - broadcastReceiver.onReceive(getContext(), null); + // Listen for changes in device orientation at the default rate that is suitable for monitoring + // typical screen orientation changes. + orientationEventListener = createOrientationEventListener(); + orientationEventListener.enable(); + } + + @VisibleForTesting + @NonNull + /** + * Creates an {@link OrientationEventListener} that will call the callback method of the {@link + * DeviceOrientationManagerProxyApi} whenever it is notified of a new device orientation and this + * {@code DeviceOrientationManager} instance determines that the orientation of the device {@link + * Configuration} has changed. + */ + protected OrientationEventListener createOrientationEventListener() { + return new OrientationEventListener(getContext()) { + @Override + public void onOrientationChanged(int orientation) { + handleUIOrientationChange(); + } + }; } /** Stops listening for orientation updates. */ public void stop() { - if (broadcastReceiver == null) { + if (orientationEventListener == null) { return; } - getContext().unregisterReceiver(broadcastReceiver); - broadcastReceiver = null; - lastOrientation = null; + + orientationEventListener.disable(); + orientationEventListener = null; } /** @@ -87,8 +98,7 @@ void handleUIOrientationChange() { } /** - * Handles orientation changes coming from either the device's sensors or the - * OrientationIntentFilter. + * Handles orientation changes coming from the device's sensors. * *

This method is visible for testing purposes only and should never be used outside this * class. diff --git a/packages/camera/camera_android_camerax/android/src/test/java/io/flutter/plugins/camerax/DeviceOrientationManagerTest.java b/packages/camera/camera_android_camerax/android/src/test/java/io/flutter/plugins/camerax/DeviceOrientationManagerTest.java index b75774f5e21..56456cf4ecc 100644 --- a/packages/camera/camera_android_camerax/android/src/test/java/io/flutter/plugins/camerax/DeviceOrientationManagerTest.java +++ b/packages/camera/camera_android_camerax/android/src/test/java/io/flutter/plugins/camerax/DeviceOrientationManagerTest.java @@ -5,11 +5,13 @@ package io.flutter.plugins.camerax; import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNull; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.doNothing; import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.mockStatic; import static org.mockito.Mockito.never; +import static org.mockito.Mockito.spy; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; @@ -18,15 +20,14 @@ import android.content.Context; import android.content.res.Configuration; import android.content.res.Resources; -import android.provider.Settings; import android.view.Display; +import android.view.OrientationEventListener; import android.view.Surface; import androidx.annotation.NonNull; import androidx.annotation.Nullable; import io.flutter.embedding.engine.systemchannels.PlatformChannel.DeviceOrientation; import org.junit.Before; import org.junit.Test; -import org.mockito.MockedStatic; public class DeviceOrientationManagerTest { private Activity mockActivity; @@ -63,21 +64,40 @@ Display getDisplay() { } @Test - public void handleUIOrientationChange_shouldSendMessageWhenSensorAccessIsAllowed() { - try (MockedStatic mockedSystem = mockStatic(Settings.System.class)) { - mockedSystem - .when( - () -> - Settings.System.getInt(any(), eq(Settings.System.ACCELEROMETER_ROTATION), eq(0))) - .thenReturn(0); - setUpUIOrientationMocks(Configuration.ORIENTATION_LANDSCAPE, Surface.ROTATION_0); - - deviceOrientationManager.handleUIOrientationChange(); - } + public void start_createsExpectedOrientationEventListener() { + DeviceOrientationManager deviceOrientationManagerSpy = spy(deviceOrientationManager); - verify(mockApi, times(1)) - .onDeviceOrientationChanged( - eq(deviceOrientationManager), eq(DeviceOrientation.LANDSCAPE_LEFT.toString()), any()); + doNothing().when(deviceOrientationManagerSpy).handleUIOrientationChange(); + + deviceOrientationManagerSpy.start(); + deviceOrientationManagerSpy.orientationEventListener.onOrientationChanged( + /* some device orientation */ 3); + + verify(deviceOrientationManagerSpy).handleUIOrientationChange(); + } + + @Test + public void start_enablesOrientationEventListener() { + DeviceOrientationManager deviceOrientationManagerSpy = spy(deviceOrientationManager); + OrientationEventListener mockOrientationEventListener = mock(OrientationEventListener.class); + + when(deviceOrientationManagerSpy.createOrientationEventListener()) + .thenReturn(mockOrientationEventListener); + + deviceOrientationManagerSpy.start(); + + verify(mockOrientationEventListener).enable(); + } + + @Test + public void stop_disablesOrientationListener() { + OrientationEventListener mockOrientationEventListener = mock(OrientationEventListener.class); + deviceOrientationManager.orientationEventListener = mockOrientationEventListener; + + deviceOrientationManager.stop(); + + verify(mockOrientationEventListener).disable(); + assertNull(deviceOrientationManager.orientationEventListener); } @Test diff --git a/packages/camera/camera_android_camerax/pubspec.yaml b/packages/camera/camera_android_camerax/pubspec.yaml index 8eb65f428cf..2b1df1be215 100644 --- a/packages/camera/camera_android_camerax/pubspec.yaml +++ b/packages/camera/camera_android_camerax/pubspec.yaml @@ -2,7 +2,7 @@ name: camera_android_camerax description: Android implementation of the camera plugin using the CameraX library. repository: https://github.com/flutter/packages/tree/main/packages/camera/camera_android_camerax issue_tracker: https://github.com/flutter/flutter/issues?q=is%3Aissue+is%3Aopen+label%3A%22p%3A+camera%22 -version: 0.6.16 +version: 0.6.17 environment: sdk: ^3.6.0