diff --git a/ci/licenses_golden/licenses_flutter b/ci/licenses_golden/licenses_flutter index 92e593af6f793..a7db8eb3b6b43 100755 --- a/ci/licenses_golden/licenses_flutter +++ b/ci/licenses_golden/licenses_flutter @@ -694,6 +694,7 @@ FILE: ../../../flutter/shell/common/canvas_spy_unittests.cc FILE: ../../../flutter/shell/common/context_options.cc FILE: ../../../flutter/shell/common/context_options.h FILE: ../../../flutter/shell/common/dart_native_benchmarks.cc +FILE: ../../../flutter/shell/common/display.cc FILE: ../../../flutter/shell/common/display.h FILE: ../../../flutter/shell/common/display_manager.cc FILE: ../../../flutter/shell/common/display_manager.h @@ -773,6 +774,8 @@ FILE: ../../../flutter/shell/platform/android/AndroidManifest.xml FILE: ../../../flutter/shell/platform/android/android_context_gl.cc FILE: ../../../flutter/shell/platform/android/android_context_gl.h FILE: ../../../flutter/shell/platform/android/android_context_gl_unittests.cc +FILE: ../../../flutter/shell/platform/android/android_display.cc +FILE: ../../../flutter/shell/platform/android/android_display.h FILE: ../../../flutter/shell/platform/android/android_environment_gl.cc FILE: ../../../flutter/shell/platform/android/android_environment_gl.h FILE: ../../../flutter/shell/platform/android/android_exports.lst diff --git a/shell/common/BUILD.gn b/shell/common/BUILD.gn index 407dd7ff0d7b8..62112b4b9d917 100644 --- a/shell/common/BUILD.gn +++ b/shell/common/BUILD.gn @@ -66,6 +66,7 @@ source_set("common") { "canvas_spy.h", "context_options.cc", "context_options.h", + "display.cc", "display.h", "display_manager.cc", "display_manager.h", diff --git a/shell/common/display.cc b/shell/common/display.cc new file mode 100644 index 0000000000000..05feded91be5a --- /dev/null +++ b/shell/common/display.cc @@ -0,0 +1,11 @@ +// Copyright 2013 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "flutter/shell/common/display.h" + +namespace flutter { +double Display::GetRefreshRate() const { + return refresh_rate_; +} +} // namespace flutter diff --git a/shell/common/display.h b/shell/common/display.h index 2c7e0fa88ff73..37e964a217f34 100644 --- a/shell/common/display.h +++ b/shell/common/display.h @@ -7,6 +7,8 @@ #include +#include "flutter/fml/macros.h" + namespace flutter { /// Unique ID per display that is stable until the Flutter application restarts. @@ -36,11 +38,11 @@ class Display { explicit Display(double refresh_rate) : display_id_({}), refresh_rate_(refresh_rate) {} - ~Display() = default; + virtual ~Display() = default; // Get the display's maximum refresh rate in the unit of frame per second. // Return `kUnknownDisplayRefreshRate` if the refresh rate is unknown. - double GetRefreshRate() const { return refresh_rate_; } + virtual double GetRefreshRate() const; /// Returns the `DisplayId` of the display. std::optional GetDisplayId() const { return display_id_; } @@ -48,6 +50,8 @@ class Display { private: std::optional display_id_; double refresh_rate_; + + FML_DISALLOW_COPY_AND_ASSIGN(Display); }; } // namespace flutter diff --git a/shell/common/display_manager.cc b/shell/common/display_manager.cc index a30c80b1a31e8..75211308de727 100644 --- a/shell/common/display_manager.cc +++ b/shell/common/display_manager.cc @@ -18,18 +18,19 @@ double DisplayManager::GetMainDisplayRefreshRate() const { if (displays_.empty()) { return kUnknownDisplayRefreshRate; } else { - return displays_[0].GetRefreshRate(); + return displays_[0]->GetRefreshRate(); } } -void DisplayManager::HandleDisplayUpdates(DisplayUpdateType update_type, - std::vector displays) { +void DisplayManager::HandleDisplayUpdates( + DisplayUpdateType update_type, + std::vector> displays) { std::scoped_lock lock(displays_mutex_); CheckDisplayConfiguration(displays); switch (update_type) { case DisplayUpdateType::kStartup: FML_CHECK(displays_.empty()); - displays_ = displays; + displays_ = std::move(displays); return; default: FML_CHECK(false) << "Unknown DisplayUpdateType."; @@ -37,11 +38,11 @@ void DisplayManager::HandleDisplayUpdates(DisplayUpdateType update_type, } void DisplayManager::CheckDisplayConfiguration( - std::vector displays) const { + const std::vector>& displays) const { FML_CHECK(!displays.empty()); if (displays.size() > 1) { for (auto& display : displays) { - FML_CHECK(display.GetDisplayId().has_value()); + FML_CHECK(display->GetDisplayId().has_value()); } } } diff --git a/shell/common/display_manager.h b/shell/common/display_manager.h index aa4bbadbb8618..774373d54e70b 100644 --- a/shell/common/display_manager.h +++ b/shell/common/display_manager.h @@ -40,18 +40,19 @@ class DisplayManager { /// Handles the display updates. void HandleDisplayUpdates(DisplayUpdateType update_type, - std::vector displays); + std::vector> displays); private: /// Guards `displays_` vector. mutable std::mutex displays_mutex_; - std::vector displays_; + std::vector> displays_; /// Checks that the provided display configuration is valid. Currently this /// ensures that all the displays have an id in the case there are multiple /// displays. In case where there is a single display, it is valid for the /// display to not have an id. - void CheckDisplayConfiguration(std::vector displays) const; + void CheckDisplayConfiguration( + const std::vector>& displays) const; }; } // namespace flutter diff --git a/shell/common/shell.cc b/shell/common/shell.cc index d78f28b406424..81320ab95bafd 100644 --- a/shell/common/shell.cc +++ b/shell/common/shell.cc @@ -1867,8 +1867,8 @@ void Shell::SetGpuAvailability(GpuAvailability availability) { } void Shell::OnDisplayUpdates(DisplayUpdateType update_type, - std::vector displays) { - display_manager_->HandleDisplayUpdates(update_type, displays); + std::vector> displays) { + display_manager_->HandleDisplayUpdates(update_type, std::move(displays)); } fml::TimePoint Shell::GetCurrentTimePoint() { diff --git a/shell/common/shell.h b/shell/common/shell.h index 3884299d19294..b0b1994fa8485 100644 --- a/shell/common/shell.h +++ b/shell/common/shell.h @@ -373,7 +373,7 @@ class Shell final : public PlatformView::Delegate, /// @brief Notifies the display manager of the updates. /// void OnDisplayUpdates(DisplayUpdateType update_type, - std::vector displays); + std::vector> displays); //---------------------------------------------------------------------------- /// @brief Queries the `DisplayManager` for the main display refresh rate. diff --git a/shell/platform/android/BUILD.gn b/shell/platform/android/BUILD.gn index 46b6d148a12d9..aaffebd273d89 100644 --- a/shell/platform/android/BUILD.gn +++ b/shell/platform/android/BUILD.gn @@ -64,6 +64,8 @@ source_set("flutter_shell_native_src") { "$root_build_dir/flutter_icu/icudtl.o", "android_context_gl.cc", "android_context_gl.h", + "android_display.cc", + "android_display.h", "android_environment_gl.cc", "android_environment_gl.h", "android_external_texture_gl.cc", diff --git a/shell/platform/android/android_display.cc b/shell/platform/android/android_display.cc new file mode 100644 index 0000000000000..edd378bff15d6 --- /dev/null +++ b/shell/platform/android/android_display.cc @@ -0,0 +1,19 @@ +// Copyright 2013 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "flutter/shell/platform/android/android_display.h" +#include "android_display.h" + +namespace flutter { + +AndroidDisplay::AndroidDisplay( + std::shared_ptr jni_facade) + : Display(jni_facade->GetDisplayRefreshRate()), + jni_facade_(std::move(jni_facade)) {} + +double AndroidDisplay::GetRefreshRate() const { + return jni_facade_->GetDisplayRefreshRate(); +} + +} // namespace flutter diff --git a/shell/platform/android/android_display.h b/shell/platform/android/android_display.h new file mode 100644 index 0000000000000..524779af2f067 --- /dev/null +++ b/shell/platform/android/android_display.h @@ -0,0 +1,33 @@ +// Copyright 2013 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef FLUTTER_SHELL_PLATFORM_ANDROID_DISPLAY_H_ +#define FLUTTER_SHELL_PLATFORM_ANDROID_DISPLAY_H_ + +#include + +#include "flutter/fml/macros.h" +#include "flutter/shell/common/display.h" +#include "flutter/shell/platform/android/jni/platform_view_android_jni.h" + +namespace flutter { + +/// A |Display| that listens to refresh rate changes. +class AndroidDisplay : public Display { + public: + explicit AndroidDisplay(std::shared_ptr jni_facade); + ~AndroidDisplay() = default; + + // |Display| + double GetRefreshRate() const override; + + private: + std::shared_ptr jni_facade_; + + FML_DISALLOW_COPY_AND_ASSIGN(AndroidDisplay); +}; + +} // namespace flutter + +#endif // FLUTTER_SHELL_PLATFORM_ANDROID_DISPLAY_H_ diff --git a/shell/platform/android/android_shell_holder.cc b/shell/platform/android/android_shell_holder.cc index b85db32a6d1cc..e9b287e734ddf 100644 --- a/shell/platform/android/android_shell_holder.cc +++ b/shell/platform/android/android_shell_holder.cc @@ -25,6 +25,7 @@ #include "flutter/shell/common/rasterizer.h" #include "flutter/shell/common/run_configuration.h" #include "flutter/shell/common/thread_host.h" +#include "flutter/shell/platform/android/android_display.h" #include "flutter/shell/platform/android/android_image_generator.h" #include "flutter/shell/platform/android/context/android_context.h" #include "flutter/shell/platform/android/platform_view_android.h" @@ -61,8 +62,10 @@ AndroidShellHolder::AndroidShellHolder( .enable_software_rendering // use software rendering ); weak_platform_view = platform_view_android->GetWeakPtr(); - auto display = Display(jni_facade->GetDisplayRefreshRate()); - shell.OnDisplayUpdates(DisplayUpdateType::kStartup, {display}); + std::vector> displays; + displays.push_back(std::make_unique(jni_facade)); + shell.OnDisplayUpdates(DisplayUpdateType::kStartup, + std::move(displays)); return platform_view_android; }; @@ -209,8 +212,10 @@ std::unique_ptr AndroidShellHolder::Spawn( android_context // Android context ); weak_platform_view = platform_view_android->GetWeakPtr(); - auto display = Display(jni_facade->GetDisplayRefreshRate()); - shell.OnDisplayUpdates(DisplayUpdateType::kStartup, {display}); + std::vector> displays; + displays.push_back(std::make_unique(jni_facade)); + shell.OnDisplayUpdates(DisplayUpdateType::kStartup, + std::move(displays)); return platform_view_android; }; diff --git a/shell/platform/android/io/flutter/embedding/engine/FlutterJNI.java b/shell/platform/android/io/flutter/embedding/engine/FlutterJNI.java index ac484f21f4601..24114ed3a424e 100644 --- a/shell/platform/android/io/flutter/embedding/engine/FlutterJNI.java +++ b/shell/platform/android/io/flutter/embedding/engine/FlutterJNI.java @@ -186,9 +186,15 @@ private static native void nativeInit( // END methods related to FlutterLoader @Nullable private static AsyncWaitForVsyncDelegate asyncWaitForVsyncDelegate; - // This should also be updated by FlutterView when it is attached to a Display. - // The initial value of 0.0 indicates unknown refresh rate. - private static float refreshRateFPS = 0.0f; + + /** + * This value is updated by the VsyncWaiter when it is initialized. + * + *

On API 17+, it is updated whenever the default display refresh rate changes. + * + *

It is defaulted to 60. + */ + private static float refreshRateFPS = 60.0f; // This is set from native code via JNI. @Nullable private static String observatoryUri; @@ -216,19 +222,34 @@ public static String getObservatoryUri() { return observatoryUri; } - public static void setRefreshRateFPS(float refreshRateFPS) { - if (FlutterJNI.setRefreshRateFPSCalled) { - Log.w(TAG, "FlutterJNI.setRefreshRateFPS called more than once"); - } - + /** + * Notifies the engine about the refresh rate of the display when the API level is below 30. + * + *

For API 30 and above, this value is ignored. + * + *

Calling this method multiple times will update the refresh rate for the next vsync period. + * However, callers should avoid calling {@link android.view.Display#getRefreshRate} frequently, + * since it is expensive on some vendor implementations. + * + * @param refreshRateFPS The refresh rate in nanoseconds. + */ + public void setRefreshRateFPS(float refreshRateFPS) { + // This is ok because it only ever tracks the refresh rate of the main + // display. If we ever need to support the refresh rate of other displays + // on Android we will need to refactor this. Static lookup makes things a + // bit easier on the C++ side. FlutterJNI.refreshRateFPS = refreshRateFPS; - FlutterJNI.setRefreshRateFPSCalled = true; } - private static boolean setRefreshRateFPSCalled = false; - - // TODO(mattcarroll): add javadocs - public static void setAsyncWaitForVsyncDelegate(@Nullable AsyncWaitForVsyncDelegate delegate) { + /** + * The Android vsync waiter implementation in C++ needs to know when a vsync signal arrives, which + * is obtained via Java API. The delegate set here is called on the C++ side when the engine is + * ready to wait for the next vsync signal. The delegate is expected to add a postFrameCallback to + * the {@link android.view.Choreographer}, and call {@link nativeOnVsync} to notify the engine. + * + * @param delegate The delegate that will call the engine back on the next vsync signal. + */ + public void setAsyncWaitForVsyncDelegate(@Nullable AsyncWaitForVsyncDelegate delegate) { asyncWaitForVsyncDelegate = delegate; } @@ -243,9 +264,15 @@ private static void asyncWaitForVsync(final long cookie) { } } - // TODO(mattcarroll): add javadocs - public static native void nativeOnVsync( - long frameDelayNanos, long refreshPeriodNanos, long cookie); + /** + * Notifies the engine that the Choreographer has signaled a vsync. + * + * @param frameDelayNanos The time in nanoseconds when the frame started being rendered, + * subtracted from the {@link System#nanoTime} timebase. + * @param refreshPeriodNanos The display refresh period in nanoseconds. + * @param cookie An opaque handle to the C++ VSyncWaiter object. + */ + public native void nativeOnVsync(long frameDelayNanos, long refreshPeriodNanos, long cookie); // TODO(mattcarroll): add javadocs @NonNull @@ -337,8 +364,7 @@ public long performNativeAttach(@NonNull FlutterJNI flutterJNI) { * #attachToNative()}. * *

Static methods that should be only called once such as {@link #init(Context, String[], - * String, String, String, long)} or {@link #setRefreshRateFPS(float)} shouldn't be called again - * on the spawned FlutterJNI instance. + * String, String, String, long)} shouldn't be called again on the spawned FlutterJNI instance. */ @UiThread @NonNull diff --git a/shell/platform/android/io/flutter/embedding/engine/loader/FlutterLoader.java b/shell/platform/android/io/flutter/embedding/engine/loader/FlutterLoader.java index f0383a377e10e..0a3c81e87bb68 100644 --- a/shell/platform/android/io/flutter/embedding/engine/loader/FlutterLoader.java +++ b/shell/platform/android/io/flutter/embedding/engine/loader/FlutterLoader.java @@ -15,7 +15,6 @@ import android.os.Handler; import android.os.Looper; import android.os.SystemClock; -import android.view.Display; import android.view.WindowManager; import androidx.annotation.NonNull; import androidx.annotation.Nullable; @@ -148,18 +147,19 @@ public void startInitialization(@NonNull Context applicationContext, @NonNull Se initStartTimestampMillis = SystemClock.uptimeMillis(); flutterApplicationInfo = ApplicationInfoLoader.load(appContext); - float fps; - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { - final DisplayManager dm = appContext.getSystemService(DisplayManager.class); - final Display primaryDisplay = dm.getDisplay(Display.DEFAULT_DISPLAY); - fps = primaryDisplay.getRefreshRate(); + VsyncWaiter waiter; + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1 /* 17 */) { + final DisplayManager dm = + (DisplayManager) appContext.getSystemService(Context.DISPLAY_SERVICE); + waiter = VsyncWaiter.getInstance(dm, flutterJNI); } else { - fps = + float fps = ((WindowManager) appContext.getSystemService(Context.WINDOW_SERVICE)) .getDefaultDisplay() .getRefreshRate(); + waiter = VsyncWaiter.getInstance(fps, flutterJNI); } - VsyncWaiter.getInstance(fps).init(); + waiter.init(); // Use a background thread for initialization tasks that require disk access. Callable initTask = diff --git a/shell/platform/android/io/flutter/view/VsyncWaiter.java b/shell/platform/android/io/flutter/view/VsyncWaiter.java index 714511f5bb3fa..200eb784675a0 100644 --- a/shell/platform/android/io/flutter/view/VsyncWaiter.java +++ b/shell/platform/android/io/flutter/view/VsyncWaiter.java @@ -4,24 +4,85 @@ package io.flutter.view; +import android.annotation.TargetApi; +import android.hardware.display.DisplayManager; import android.view.Choreographer; +import android.view.Display; import androidx.annotation.NonNull; +import androidx.annotation.VisibleForTesting; import io.flutter.embedding.engine.FlutterJNI; // TODO(mattcarroll): add javadoc. public class VsyncWaiter { + @TargetApi(17) + class DisplayListener implements DisplayManager.DisplayListener { + DisplayListener(DisplayManager displayManager) { + this.displayManager = displayManager; + } + + private DisplayManager displayManager; + + void register() { + displayManager.registerDisplayListener(this, null); + } + + @Override + public void onDisplayAdded(int displayId) {} + + @Override + public void onDisplayRemoved(int displayId) {} + + @Override + public void onDisplayChanged(int displayId) { + if (displayId == Display.DEFAULT_DISPLAY) { + final Display primaryDisplay = displayManager.getDisplay(Display.DEFAULT_DISPLAY); + float fps = primaryDisplay.getRefreshRate(); + VsyncWaiter.this.refreshPeriodNanos = (long) (1000000000.0 / fps); + VsyncWaiter.this.flutterJNI.setRefreshRateFPS(fps); + } + } + } + private static VsyncWaiter instance; + private static DisplayListener listener; + private long refreshPeriodNanos = -1; + private FlutterJNI flutterJNI; @NonNull - public static VsyncWaiter getInstance(float fps) { + public static VsyncWaiter getInstance(float fps, FlutterJNI flutterJNI) { if (instance == null) { - instance = new VsyncWaiter(fps); + instance = new VsyncWaiter(flutterJNI); } + flutterJNI.setRefreshRateFPS(fps); + instance.refreshPeriodNanos = (long) (1000000000.0 / fps); return instance; } - private final float fps; - private final long refreshPeriodNanos; + @TargetApi(17) + @NonNull + public static VsyncWaiter getInstance(DisplayManager displayManager, FlutterJNI flutterJNI) { + if (instance == null) { + instance = new VsyncWaiter(flutterJNI); + } + if (listener == null) { + listener = instance.new DisplayListener(displayManager); + listener.register(); + } + if (instance.refreshPeriodNanos == -1) { + final Display primaryDisplay = displayManager.getDisplay(Display.DEFAULT_DISPLAY); + float fps = primaryDisplay.getRefreshRate(); + instance.refreshPeriodNanos = (long) (1000000000.0 / fps); + flutterJNI.setRefreshRateFPS(fps); + } + return instance; + } + + // For tests, to reset the singleton between tests. + @VisibleForTesting + public static void reset() { + instance = null; + listener = null; + } private final FlutterJNI.AsyncWaitForVsyncDelegate asyncWaitForVsyncDelegate = new FlutterJNI.AsyncWaitForVsyncDelegate() { @@ -36,21 +97,17 @@ public void doFrame(long frameTimeNanos) { if (delay < 0) { delay = 0; } - FlutterJNI.nativeOnVsync(delay, refreshPeriodNanos, cookie); + flutterJNI.nativeOnVsync(delay, refreshPeriodNanos, cookie); } }); } }; - private VsyncWaiter(float fps) { - this.fps = fps; - refreshPeriodNanos = (long) (1000000000.0 / fps); + private VsyncWaiter(FlutterJNI flutterJNI) { + this.flutterJNI = flutterJNI; } public void init() { - FlutterJNI.setAsyncWaitForVsyncDelegate(asyncWaitForVsyncDelegate); - - // TODO(mattcarroll): look into moving FPS reporting to a plugin - FlutterJNI.setRefreshRateFPS(fps); + flutterJNI.setAsyncWaitForVsyncDelegate(asyncWaitForVsyncDelegate); } } diff --git a/shell/platform/android/test/io/flutter/view/VsyncWaiterTest.java b/shell/platform/android/test/io/flutter/view/VsyncWaiterTest.java new file mode 100644 index 0000000000000..7b08967b31c9d --- /dev/null +++ b/shell/platform/android/test/io/flutter/view/VsyncWaiterTest.java @@ -0,0 +1,103 @@ +// Copyright 2013 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +package io.flutter.view; + +import static org.mockito.Matchers.any; +import static org.mockito.Matchers.anyLong; +import static org.mockito.Matchers.eq; +import static org.mockito.Matchers.isNull; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; +import static org.robolectric.Shadows.shadowOf; + +import android.annotation.TargetApi; +import android.hardware.display.DisplayManager; +import android.os.Looper; +import android.view.Display; +import io.flutter.embedding.engine.FlutterJNI; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.ArgumentCaptor; +import org.robolectric.RobolectricTestRunner; +import org.robolectric.annotation.Config; + +@Config(manifest = Config.NONE) +@RunWith(RobolectricTestRunner.class) +public class VsyncWaiterTest { + @Before + public void setUp() { + VsyncWaiter.reset(); + } + + @Test + public void itSetsFpsBelowApi17() { + FlutterJNI mockFlutterJNI = mock(FlutterJNI.class); + VsyncWaiter waiter = VsyncWaiter.getInstance(10.0f, mockFlutterJNI); + verify(mockFlutterJNI, times(1)).setRefreshRateFPS(10.0f); + + waiter.init(); + + ArgumentCaptor delegateCaptor = + ArgumentCaptor.forClass(FlutterJNI.AsyncWaitForVsyncDelegate.class); + verify(mockFlutterJNI, times(1)).setAsyncWaitForVsyncDelegate(delegateCaptor.capture()); + delegateCaptor.getValue().asyncWaitForVsync(1); + shadowOf(Looper.getMainLooper()).idle(); + verify(mockFlutterJNI, times(1)).nativeOnVsync(anyLong(), eq(1000000000l / 10l), eq(1l)); + } + + @TargetApi(17) + @Test + public void itSetsFpsWhenDisplayManagerUpdates() { + FlutterJNI mockFlutterJNI = mock(FlutterJNI.class); + DisplayManager mockDisplayManager = mock(DisplayManager.class); + Display mockDisplay = mock(Display.class); + ArgumentCaptor displayListenerCaptor = + ArgumentCaptor.forClass(VsyncWaiter.DisplayListener.class); + when(mockDisplayManager.getDisplay(Display.DEFAULT_DISPLAY)).thenReturn(mockDisplay); + + VsyncWaiter waiter = VsyncWaiter.getInstance(mockDisplayManager, mockFlutterJNI); + verify(mockDisplayManager, times(1)) + .registerDisplayListener(displayListenerCaptor.capture(), isNull()); + + when(mockDisplay.getRefreshRate()).thenReturn(90.0f); + displayListenerCaptor.getValue().onDisplayChanged(Display.DEFAULT_DISPLAY); + verify(mockFlutterJNI, times(1)).setRefreshRateFPS(90.0f); + + waiter.init(); + + ArgumentCaptor delegateCaptor = + ArgumentCaptor.forClass(FlutterJNI.AsyncWaitForVsyncDelegate.class); + verify(mockFlutterJNI, times(1)).setAsyncWaitForVsyncDelegate(delegateCaptor.capture()); + delegateCaptor.getValue().asyncWaitForVsync(1); + shadowOf(Looper.getMainLooper()).idle(); + verify(mockFlutterJNI, times(1)).nativeOnVsync(anyLong(), eq(1000000000l / 90l), eq(1l)); + + when(mockDisplay.getRefreshRate()).thenReturn(60.0f); + displayListenerCaptor.getValue().onDisplayChanged(Display.DEFAULT_DISPLAY); + verify(mockFlutterJNI, times(1)).setRefreshRateFPS(60.0f); + + delegateCaptor.getValue().asyncWaitForVsync(1); + shadowOf(Looper.getMainLooper()).idle(); + verify(mockFlutterJNI, times(1)).nativeOnVsync(anyLong(), eq(1000000000l / 60l), eq(1l)); + } + + @TargetApi(17) + @Test + public void itSetsFpsWhenDisplayManagerDoesNotUpdate() { + FlutterJNI mockFlutterJNI = mock(FlutterJNI.class); + DisplayManager mockDisplayManager = mock(DisplayManager.class); + Display mockDisplay = mock(Display.class); + when(mockDisplayManager.getDisplay(Display.DEFAULT_DISPLAY)).thenReturn(mockDisplay); + when(mockDisplay.getRefreshRate()).thenReturn(90.0f); + + VsyncWaiter waiter = VsyncWaiter.getInstance(mockDisplayManager, mockFlutterJNI); + verify(mockDisplayManager, times(1)).registerDisplayListener(any(), isNull()); + + verify(mockFlutterJNI, times(1)).setRefreshRateFPS(90.0f); + } +} diff --git a/shell/platform/darwin/ios/framework/Source/FlutterEngine.mm b/shell/platform/darwin/ios/framework/Source/FlutterEngine.mm index 4fc14c09d2865..337dca9fa6e31 100644 --- a/shell/platform/darwin/ios/framework/Source/FlutterEngine.mm +++ b/shell/platform/darwin/ios/framework/Source/FlutterEngine.mm @@ -689,8 +689,9 @@ - (BOOL)createShell:(NSString*)entrypoint - (void)initializeDisplays { double refresh_rate = [DisplayLinkManager displayRefreshRate]; - auto display = flutter::Display(refresh_rate); - _shell->OnDisplayUpdates(flutter::DisplayUpdateType::kStartup, {display}); + std::vector> displays; + displays.push_back(std::make_unique(refresh_rate)); + _shell->OnDisplayUpdates(flutter::DisplayUpdateType::kStartup, std::move(displays)); } - (BOOL)run { diff --git a/shell/platform/embedder/embedder.cc b/shell/platform/embedder/embedder.cc index 6bb26ef7efbdc..4afe2f2e9bab8 100644 --- a/shell/platform/embedder/embedder.cc +++ b/shell/platform/embedder/embedder.cc @@ -2267,18 +2267,19 @@ FlutterEngineResult FlutterEngineNotifyDisplayUpdate( switch (update_type) { case kFlutterEngineDisplaysUpdateTypeStartup: { - std::vector displays; + std::vector> displays; for (size_t i = 0; i < display_count; i++) { - flutter::Display display = - flutter::Display(embedder_displays[i].refresh_rate); - if (!embedder_displays[i].single_display) { - display = flutter::Display(embedder_displays[i].display_id, - embedder_displays[i].refresh_rate); + if (embedder_displays[i].single_display) { + displays.push_back(std::make_unique( + embedder_displays[i].refresh_rate)); + } else { + displays.push_back(std::make_unique( + embedder_displays[i].display_id, + embedder_displays[i].refresh_rate)); } - displays.push_back(display); } engine->GetShell().OnDisplayUpdates(flutter::DisplayUpdateType::kStartup, - displays); + std::move(displays)); return kSuccess; } default: diff --git a/tools/android_lint/project.xml b/tools/android_lint/project.xml index a5d3805d734ce..916da6b37a313 100644 --- a/tools/android_lint/project.xml +++ b/tools/android_lint/project.xml @@ -56,6 +56,7 @@ +