diff --git a/shell/platform/android/io/flutter/embedding/android/FlutterView.java b/shell/platform/android/io/flutter/embedding/android/FlutterView.java index 2d10a20dba1..1412d9daaa8 100644 --- a/shell/platform/android/io/flutter/embedding/android/FlutterView.java +++ b/shell/platform/android/io/flutter/embedding/android/FlutterView.java @@ -4,8 +4,8 @@ package io.flutter.embedding.android; -import android.annotation.TargetApi; import android.annotation.SuppressLint; +import android.annotation.TargetApi; import android.content.Context; import android.content.res.Configuration; import android.graphics.Insets; @@ -39,6 +39,7 @@ import io.flutter.embedding.engine.FlutterEngine; import io.flutter.embedding.engine.renderer.FlutterRenderer; import io.flutter.embedding.engine.renderer.FlutterUiDisplayListener; import io.flutter.embedding.engine.renderer.RenderSurface; +import io.flutter.embedding.engine.systemchannels.SettingsChannel; import io.flutter.plugin.editing.TextInputPlugin; import io.flutter.plugin.platform.PlatformViewsController; import io.flutter.view.AccessibilityBridge; @@ -761,10 +762,19 @@ public class FlutterView extends FrameLayout { * * FlutterEngine must be non-null when this method is invoked. */ - private void sendUserSettingsToFlutter() { - flutterEngine.getSettingsChannel().startMessage() + @VisibleForTesting + /* package */ void sendUserSettingsToFlutter() { + // Lookup the current brightness of the Android OS. + boolean isNightModeOn = (getResources().getConfiguration().uiMode & Configuration.UI_MODE_NIGHT_MASK) == Configuration.UI_MODE_NIGHT_YES; + SettingsChannel.PlatformBrightness brightness = isNightModeOn + ? SettingsChannel.PlatformBrightness.dark + : SettingsChannel.PlatformBrightness.light; + + flutterEngine.getSettingsChannel() + .startMessage() .setTextScaleFactor(getResources().getConfiguration().fontScale) .setUse24HourFormat(DateFormat.is24HourFormat(getContext())) + .setPlatformBrightness(brightness) .send(); } diff --git a/shell/platform/android/test/io/flutter/embedding/android/FlutterViewTest.java b/shell/platform/android/test/io/flutter/embedding/android/FlutterViewTest.java index fe87fc8954d..239045b07d4 100644 --- a/shell/platform/android/test/io/flutter/embedding/android/FlutterViewTest.java +++ b/shell/platform/android/test/io/flutter/embedding/android/FlutterViewTest.java @@ -1,12 +1,30 @@ package io.flutter.embedding.android; +import android.content.Context; +import android.content.res.Configuration; +import android.content.res.Resources; +import android.support.annotation.NonNull; + +import static junit.framework.TestCase.assertEquals; +import static org.mockito.Matchers.any; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.reset; import static org.mockito.Mockito.spy; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; import io.flutter.embedding.engine.FlutterJNI; +import io.flutter.embedding.engine.dart.DartExecutor; import io.flutter.embedding.engine.loader.FlutterLoader; +import io.flutter.embedding.engine.plugins.activity.ActivityControlSurface; +import io.flutter.embedding.engine.renderer.FlutterRenderer; +import io.flutter.embedding.engine.systemchannels.AccessibilityChannel; +import io.flutter.embedding.engine.systemchannels.LifecycleChannel; +import io.flutter.embedding.engine.systemchannels.LocalizationChannel; +import io.flutter.embedding.engine.systemchannels.NavigationChannel; +import io.flutter.embedding.engine.systemchannels.SettingsChannel; +import io.flutter.embedding.engine.systemchannels.SystemChannel; import io.flutter.plugin.platform.PlatformViewsController; import org.junit.Before; import org.junit.Test; @@ -14,10 +32,14 @@ import org.junit.runner.RunWith; import org.mockito.Mock; import org.mockito.MockitoAnnotations; import org.mockito.Spy; +import org.mockito.invocation.InvocationOnMock; +import org.mockito.stubbing.Answer; import org.robolectric.RobolectricTestRunner; import org.robolectric.RuntimeEnvironment; import org.robolectric.annotation.Config; +import java.util.concurrent.atomic.AtomicReference; + import io.flutter.embedding.engine.FlutterEngine; @Config(manifest = Config.NONE) @@ -44,15 +66,88 @@ public class FlutterViewTest { verify(platformViewsController, times(1)).attachToView(flutterView); } - @Test - public void detachFromFlutterEngine_alertsPlatformViews() { - FlutterView flutterView = new FlutterView(RuntimeEnvironment.application); - FlutterEngine flutterEngine = spy(new FlutterEngine(RuntimeEnvironment.application, mockFlutterLoader, mockFlutterJni)); - when(flutterEngine.getPlatformViewsController()).thenReturn(platformViewsController); + @Test + public void detachFromFlutterEngine_alertsPlatformViews() { + FlutterView flutterView = new FlutterView(RuntimeEnvironment.application); + FlutterEngine flutterEngine = spy(new FlutterEngine(RuntimeEnvironment.application, mockFlutterLoader, mockFlutterJni)); + when(flutterEngine.getPlatformViewsController()).thenReturn(platformViewsController); - flutterView.attachToFlutterEngine(flutterEngine); - flutterView.detachFromFlutterEngine(); + flutterView.attachToFlutterEngine(flutterEngine); + flutterView.detachFromFlutterEngine(); - verify(platformViewsController, times(1)).detachFromView(); - } + verify(platformViewsController, times(1)).detachFromView(); + } + + // TODO(mattcarroll): turn this into an e2e test. GitHub #42990 + @Test + public void itSendsLightPlatformBrightnessToFlutter() { + // Setup test. + AtomicReference reportedBrightness = new AtomicReference<>(); + + // FYI - The default brightness is LIGHT, which is why we don't need to configure it. + FlutterView flutterView = new FlutterView(RuntimeEnvironment.application); + FlutterEngine flutterEngine = spy(new FlutterEngine(RuntimeEnvironment.application, mockFlutterLoader, mockFlutterJni)); + + SettingsChannel fakeSettingsChannel = mock(SettingsChannel.class); + SettingsChannel.MessageBuilder fakeMessageBuilder = mock(SettingsChannel.MessageBuilder.class); + when(fakeMessageBuilder.setTextScaleFactor(any(Float.class))).thenReturn(fakeMessageBuilder); + when(fakeMessageBuilder.setUse24HourFormat(any(Boolean.class))).thenReturn(fakeMessageBuilder); + when(fakeMessageBuilder.setPlatformBrightness(any(SettingsChannel.PlatformBrightness.class))).thenAnswer(new Answer() { + @Override + public SettingsChannel.MessageBuilder answer(InvocationOnMock invocation) throws Throwable { + reportedBrightness.set((SettingsChannel.PlatformBrightness) invocation.getArguments()[0]); + return fakeMessageBuilder; + } + }); + when(fakeSettingsChannel.startMessage()).thenReturn(fakeMessageBuilder); + when(flutterEngine.getSettingsChannel()).thenReturn(fakeSettingsChannel); + + flutterView.attachToFlutterEngine(flutterEngine); + + // Execute behavior under test. + flutterView.sendUserSettingsToFlutter(); + + // Verify results. + assertEquals(SettingsChannel.PlatformBrightness.light, reportedBrightness.get()); + } + + // TODO(mattcarroll): turn this into an e2e test. GitHub #42990 + @Test + public void itSendsDarkPlatformBrightnessToFlutter() { + // Setup test. + AtomicReference reportedBrightness = new AtomicReference<>(); + + Context spiedContext = spy(RuntimeEnvironment.application); + + Resources spiedResources = spy(spiedContext.getResources()); + when(spiedContext.getResources()).thenReturn(spiedResources); + + Configuration spiedConfiguration = spy(spiedResources.getConfiguration()); + spiedConfiguration.uiMode = (spiedResources.getConfiguration().uiMode | Configuration.UI_MODE_NIGHT_YES) & ~Configuration.UI_MODE_NIGHT_NO; + when(spiedResources.getConfiguration()).thenReturn(spiedConfiguration); + + FlutterView flutterView = new FlutterView(spiedContext); + FlutterEngine flutterEngine = spy(new FlutterEngine(RuntimeEnvironment.application, mockFlutterLoader, mockFlutterJni)); + + SettingsChannel fakeSettingsChannel = mock(SettingsChannel.class); + SettingsChannel.MessageBuilder fakeMessageBuilder = mock(SettingsChannel.MessageBuilder.class); + when(fakeMessageBuilder.setTextScaleFactor(any(Float.class))).thenReturn(fakeMessageBuilder); + when(fakeMessageBuilder.setUse24HourFormat(any(Boolean.class))).thenReturn(fakeMessageBuilder); + when(fakeMessageBuilder.setPlatformBrightness(any(SettingsChannel.PlatformBrightness.class))).thenAnswer(new Answer() { + @Override + public SettingsChannel.MessageBuilder answer(InvocationOnMock invocation) throws Throwable { + reportedBrightness.set((SettingsChannel.PlatformBrightness) invocation.getArguments()[0]); + return fakeMessageBuilder; + } + }); + when(fakeSettingsChannel.startMessage()).thenReturn(fakeMessageBuilder); + when(flutterEngine.getSettingsChannel()).thenReturn(fakeSettingsChannel); + + // Execute behavior under test. + flutterView.attachToFlutterEngine(flutterEngine); + flutterView.sendUserSettingsToFlutter(); + + // Verify results. + assertEquals(SettingsChannel.PlatformBrightness.dark, reportedBrightness.get()); + } }