Skip to content

Commit c2f8201

Browse files
authored
[camera_android_camerax] Swap out BroadcastReceiver for OrientationEventListener (#9261)
Replaces usage of [`BroadcastReceiver`](https://developer.android.com/reference/android/content/BroadcastReceiver) (that is registered with the [`ACTION_CONFIGURATION_CHANGED`](https://developer.android.com/reference/android/content/Intent#ACTION_CONFIGURATION_CHANGED) intent) with an [`OrientationEventListener`](https://developer.android.com/reference/android/view/OrientationEventListener) to notify the plugin of changes in device orientation. Using an `OrientationEventListener` allows the plugin to more regularly to orientation updates, whereas listening to the `ACTION_CONFIGURATION_CHANGED` only reports changes in device configuration. This is an issue because sometimes, a device changing from a landscape orientation to another landscape orientation is not considered a change in configuration. Fixes flutter/flutter#168565. Inherently fixes flutter/flutter#163818 (no more `BroadcastReceiver`). Should also inherently fix flutter/flutter#162854 (no more `BroadcastReceiver`, but don't worry--we already stop listening to changes in device orientation when the plugin is disposed). ## Pre-Review Checklist [^1]: Regular contributors who have demonstrated familiarity with the repository guidelines only need to comment if the PR is not auto-exempted by repo tooling.
1 parent f444a1e commit c2f8201

File tree

4 files changed

+75
-39
lines changed

4 files changed

+75
-39
lines changed

packages/camera/camera_android_camerax/CHANGELOG.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,9 @@
1+
## 0.6.17
2+
3+
* Replaces `BroadcastReceiver` usage with an `OrientationEventListener` to detect changes in device
4+
orientation to fix issue where some devices do not report changes in device configuration if it
5+
is rotated between the same sort of orientation (landscape/portrait).
6+
17
## 0.6.16
28

39
* Fixes incorrect camera preview rotation for landscape-oriented devices.

packages/camera/camera_android_camerax/android/src/main/java/io/flutter/plugins/camerax/DeviceOrientationManager.java

Lines changed: 31 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -10,8 +10,10 @@
1010
import android.content.IntentFilter;
1111
import android.content.res.Configuration;
1212
import android.view.Display;
13+
import android.view.OrientationEventListener;
1314
import android.view.Surface;
1415
import androidx.annotation.NonNull;
16+
import androidx.annotation.Nullable;
1517
import androidx.annotation.VisibleForTesting;
1618
import io.flutter.embedding.engine.systemchannels.PlatformChannel;
1719
import io.flutter.embedding.engine.systemchannels.PlatformChannel.DeviceOrientation;
@@ -28,6 +30,8 @@ public class DeviceOrientationManager {
2830
private PlatformChannel.DeviceOrientation lastOrientation;
2931
private BroadcastReceiver broadcastReceiver;
3032

33+
@VisibleForTesting @Nullable protected OrientationEventListener orientationEventListener;
34+
3135
DeviceOrientationManager(DeviceOrientationManagerProxyApi api) {
3236
this.api = api;
3337
}
@@ -38,39 +42,46 @@ Context getContext() {
3842
}
3943

4044
/**
41-
* Starts listening to the device's sensors or UI for orientation updates.
45+
* Starts listening to the device's sensors for device orientation updates.
4246
*
4347
* <p>When orientation information is updated, the callback method of the {@link
4448
* DeviceOrientationManagerProxyApi} is called with the new orientation.
45-
*
46-
* <p>If the device's ACCELEROMETER_ROTATION setting is enabled the {@link
47-
* DeviceOrientationManager} will report orientation updates based on the sensor information. If
48-
* the ACCELEROMETER_ROTATION is disabled the {@link DeviceOrientationManager} will fallback to
49-
* the deliver orientation updates based on the UI orientation.
5049
*/
5150
public void start() {
5251
stop();
5352

54-
broadcastReceiver =
55-
new BroadcastReceiver() {
56-
@Override
57-
public void onReceive(Context context, Intent intent) {
58-
handleUIOrientationChange();
59-
}
60-
};
61-
getContext().registerReceiver(broadcastReceiver, orientationIntentFilter);
62-
broadcastReceiver.onReceive(getContext(), null);
53+
// Listen for changes in device orientation at the default rate that is suitable for monitoring
54+
// typical screen orientation changes.
55+
orientationEventListener = createOrientationEventListener();
56+
orientationEventListener.enable();
57+
}
58+
59+
@VisibleForTesting
60+
@NonNull
61+
/**
62+
* Creates an {@link OrientationEventListener} that will call the callback method of the {@link
63+
* DeviceOrientationManagerProxyApi} whenever it is notified of a new device orientation and this
64+
* {@code DeviceOrientationManager} instance determines that the orientation of the device {@link
65+
* Configuration} has changed.
66+
*/
67+
protected OrientationEventListener createOrientationEventListener() {
68+
return new OrientationEventListener(getContext()) {
69+
@Override
70+
public void onOrientationChanged(int orientation) {
71+
handleUIOrientationChange();
72+
}
73+
};
6374
}
6475

6576
/** Stops listening for orientation updates. */
6677
public void stop() {
67-
if (broadcastReceiver == null) {
78+
if (orientationEventListener == null) {
6879
return;
6980
}
70-
getContext().unregisterReceiver(broadcastReceiver);
71-
broadcastReceiver = null;
72-
7381
lastOrientation = null;
82+
83+
orientationEventListener.disable();
84+
orientationEventListener = null;
7485
}
7586

7687
/**
@@ -87,8 +98,7 @@ void handleUIOrientationChange() {
8798
}
8899

89100
/**
90-
* Handles orientation changes coming from either the device's sensors or the
91-
* OrientationIntentFilter.
101+
* Handles orientation changes coming from the device's sensors.
92102
*
93103
* <p>This method is visible for testing purposes only and should never be used outside this
94104
* class.

packages/camera/camera_android_camerax/android/src/test/java/io/flutter/plugins/camerax/DeviceOrientationManagerTest.java

Lines changed: 37 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -5,11 +5,13 @@
55
package io.flutter.plugins.camerax;
66

77
import static org.junit.Assert.assertEquals;
8+
import static org.junit.Assert.assertNull;
89
import static org.mockito.ArgumentMatchers.any;
910
import static org.mockito.ArgumentMatchers.eq;
11+
import static org.mockito.Mockito.doNothing;
1012
import static org.mockito.Mockito.mock;
11-
import static org.mockito.Mockito.mockStatic;
1213
import static org.mockito.Mockito.never;
14+
import static org.mockito.Mockito.spy;
1315
import static org.mockito.Mockito.times;
1416
import static org.mockito.Mockito.verify;
1517
import static org.mockito.Mockito.when;
@@ -18,15 +20,14 @@
1820
import android.content.Context;
1921
import android.content.res.Configuration;
2022
import android.content.res.Resources;
21-
import android.provider.Settings;
2223
import android.view.Display;
24+
import android.view.OrientationEventListener;
2325
import android.view.Surface;
2426
import androidx.annotation.NonNull;
2527
import androidx.annotation.Nullable;
2628
import io.flutter.embedding.engine.systemchannels.PlatformChannel.DeviceOrientation;
2729
import org.junit.Before;
2830
import org.junit.Test;
29-
import org.mockito.MockedStatic;
3031

3132
public class DeviceOrientationManagerTest {
3233
private Activity mockActivity;
@@ -63,21 +64,40 @@ Display getDisplay() {
6364
}
6465

6566
@Test
66-
public void handleUIOrientationChange_shouldSendMessageWhenSensorAccessIsAllowed() {
67-
try (MockedStatic<Settings.System> mockedSystem = mockStatic(Settings.System.class)) {
68-
mockedSystem
69-
.when(
70-
() ->
71-
Settings.System.getInt(any(), eq(Settings.System.ACCELEROMETER_ROTATION), eq(0)))
72-
.thenReturn(0);
73-
setUpUIOrientationMocks(Configuration.ORIENTATION_LANDSCAPE, Surface.ROTATION_0);
74-
75-
deviceOrientationManager.handleUIOrientationChange();
76-
}
67+
public void start_createsExpectedOrientationEventListener() {
68+
DeviceOrientationManager deviceOrientationManagerSpy = spy(deviceOrientationManager);
7769

78-
verify(mockApi, times(1))
79-
.onDeviceOrientationChanged(
80-
eq(deviceOrientationManager), eq(DeviceOrientation.LANDSCAPE_LEFT.toString()), any());
70+
doNothing().when(deviceOrientationManagerSpy).handleUIOrientationChange();
71+
72+
deviceOrientationManagerSpy.start();
73+
deviceOrientationManagerSpy.orientationEventListener.onOrientationChanged(
74+
/* some device orientation */ 3);
75+
76+
verify(deviceOrientationManagerSpy).handleUIOrientationChange();
77+
}
78+
79+
@Test
80+
public void start_enablesOrientationEventListener() {
81+
DeviceOrientationManager deviceOrientationManagerSpy = spy(deviceOrientationManager);
82+
OrientationEventListener mockOrientationEventListener = mock(OrientationEventListener.class);
83+
84+
when(deviceOrientationManagerSpy.createOrientationEventListener())
85+
.thenReturn(mockOrientationEventListener);
86+
87+
deviceOrientationManagerSpy.start();
88+
89+
verify(mockOrientationEventListener).enable();
90+
}
91+
92+
@Test
93+
public void stop_disablesOrientationListener() {
94+
OrientationEventListener mockOrientationEventListener = mock(OrientationEventListener.class);
95+
deviceOrientationManager.orientationEventListener = mockOrientationEventListener;
96+
97+
deviceOrientationManager.stop();
98+
99+
verify(mockOrientationEventListener).disable();
100+
assertNull(deviceOrientationManager.orientationEventListener);
81101
}
82102

83103
@Test

packages/camera/camera_android_camerax/pubspec.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ name: camera_android_camerax
22
description: Android implementation of the camera plugin using the CameraX library.
33
repository: https://github.com/flutter/packages/tree/main/packages/camera/camera_android_camerax
44
issue_tracker: https://github.com/flutter/flutter/issues?q=is%3Aissue+is%3Aopen+label%3A%22p%3A+camera%22
5-
version: 0.6.16
5+
version: 0.6.17
66

77
environment:
88
sdk: ^3.6.0

0 commit comments

Comments
 (0)