Using Choreographer for getting refreshrate instead of display manager

Bug: 404582684
Flag: EXEMPT bugfix
Test: Manually verified that values are same as before even after changing refresh rate `adb shell settings put system peak_refresh_rate [30/60/90/120]`
Change-Id: I5d5468d8ab81a9a5e8d8cd67a9097423b750edb3
This commit is contained in:
Sunny Goyal
2025-03-18 14:43:53 -07:00
parent 91a081f26c
commit 836a832bfe
6 changed files with 114 additions and 93 deletions
@@ -21,8 +21,10 @@ import com.android.launcher3.uioverrides.SystemApiWrapper
import com.android.launcher3.uioverrides.plugins.PluginManagerWrapperImpl
import com.android.launcher3.util.ApiWrapper
import com.android.launcher3.util.PluginManagerWrapper
import com.android.launcher3.util.window.RefreshRateTracker
import com.android.launcher3.util.window.WindowManagerProxy
import com.android.launcher3.widget.LauncherWidgetHolder.WidgetHolderFactory
import com.android.quickstep.util.ChoreographerFrameRateTracker
import com.android.quickstep.util.GestureExclusionManager
import com.android.quickstep.util.SystemWindowManagerProxy
import dagger.Binds
@@ -60,4 +62,8 @@ object StaticObjectModule {
@Provides
@JvmStatic
fun provideGestureExclusionManager(): GestureExclusionManager = GestureExclusionManager.INSTANCE
@Provides
@JvmStatic
fun provideRefreshRateTracker(): RefreshRateTracker = ChoreographerFrameRateTracker
}
@@ -0,0 +1,31 @@
/*
* Copyright (C) 2025 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.quickstep.util
import android.util.TimeUtils
import android.view.Choreographer
import com.android.launcher3.util.window.RefreshRateTracker
/** [RefreshRateTracker] using main thread [Choreographer] */
object ChoreographerFrameRateTracker : RefreshRateTracker {
override val singleFrameMs: Int
get() =
Choreographer.getMainThreadInstance()?.let {
(it.frameIntervalNanos / TimeUtils.NANOS_PER_MS).toInt().coerceAtLeast(1)
} ?: 1
}
@@ -91,6 +91,7 @@ public interface LauncherBaseAppComponent {
LoaderCursorFactory getLoaderCursorFactory();
WidgetHolderFactory getWidgetHolderFactory();
RefreshRateTracker getFrameRateProvider();
/** Builder for LauncherBaseAppComponent. */
interface Builder {
@@ -1,92 +0,0 @@
/*
* Copyright (C) 2022 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.launcher3.util.window;
import static android.view.Display.DEFAULT_DISPLAY;
import static com.android.launcher3.util.Executors.UI_HELPER_EXECUTOR;
import android.content.Context;
import android.hardware.display.DisplayManager;
import android.hardware.display.DisplayManager.DisplayListener;
import android.view.Display;
import androidx.annotation.WorkerThread;
import com.android.launcher3.dagger.ApplicationContext;
import com.android.launcher3.dagger.LauncherAppComponent;
import com.android.launcher3.dagger.LauncherAppSingleton;
import com.android.launcher3.util.DaggerSingletonObject;
import com.android.launcher3.util.DaggerSingletonTracker;
import com.android.launcher3.util.SafeCloseable;
import javax.inject.Inject;
/**
* Utility class to track refresh rate of the current device
*/
@LauncherAppSingleton
public class RefreshRateTracker implements DisplayListener, SafeCloseable {
private static final DaggerSingletonObject<RefreshRateTracker> INSTANCE =
new DaggerSingletonObject<>(LauncherAppComponent::getRefreshRateTracker);
private int mSingleFrameMs = 1;
private final DisplayManager mDM;
@Inject
RefreshRateTracker(@ApplicationContext Context context, DaggerSingletonTracker tracker) {
mDM = context.getSystemService(DisplayManager.class);
updateSingleFrameMs();
mDM.registerDisplayListener(this, UI_HELPER_EXECUTOR.getHandler());
tracker.addCloseable(this);
}
/**
* Returns the single frame time in ms
*/
public static int getSingleFrameMs(Context context) {
return INSTANCE.get(context).mSingleFrameMs;
}
@Override
public final void onDisplayAdded(int displayId) { }
@Override
public final void onDisplayRemoved(int displayId) { }
@WorkerThread
@Override
public final void onDisplayChanged(int displayId) {
if (displayId == DEFAULT_DISPLAY) {
updateSingleFrameMs();
}
}
private void updateSingleFrameMs() {
Display display = mDM.getDisplay(DEFAULT_DISPLAY);
if (display != null) {
float refreshRate = display.getRefreshRate();
mSingleFrameMs = refreshRate > 0 ? (int) (1000 / refreshRate) : 16;
}
}
@Override
public void close() {
mDM.unregisterDisplayListener(this);
}
}
@@ -0,0 +1,70 @@
/*
* Copyright (C) 2025 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.launcher3.util.window
import android.content.Context
import android.hardware.display.DisplayManager
import android.hardware.display.DisplayManager.DisplayListener
import android.view.Display.DEFAULT_DISPLAY
import com.android.launcher3.dagger.ApplicationContext
import com.android.launcher3.dagger.LauncherAppSingleton
import com.android.launcher3.dagger.LauncherComponentProvider.appComponent
import com.android.launcher3.util.DaggerSingletonTracker
import com.android.launcher3.util.Executors
import javax.inject.Inject
/** Utility class to track refresh rate of the current device */
interface RefreshRateTracker {
val singleFrameMs: Int
@LauncherAppSingleton
class RefreshRateTrackerImpl
@Inject
constructor(@ApplicationContext ctx: Context, tracker: DaggerSingletonTracker) :
RefreshRateTracker, DisplayListener {
private val displayManager: DisplayManager =
ctx.getSystemService(DisplayManager::class.java)!!.also {
it.registerDisplayListener(this, Executors.UI_HELPER_EXECUTOR.handler)
tracker.addCloseable { it.unregisterDisplayListener(this) }
}
override var singleFrameMs: Int = updateSingleFrameMs()
private fun updateSingleFrameMs(): Int {
val refreshRate = displayManager.getDisplay(DEFAULT_DISPLAY)?.refreshRate
return if (refreshRate != null && refreshRate > 0) (1000 / refreshRate).toInt() else 16
}
override fun onDisplayChanged(displayId: Int) {
if (displayId == DEFAULT_DISPLAY) {
singleFrameMs = updateSingleFrameMs()
}
}
override fun onDisplayAdded(displayId: Int) {}
override fun onDisplayRemoved(displayId: Int) {}
}
companion object {
/** Returns the single frame time in ms */
@JvmStatic fun Context.getSingleFrameMs() = appComponent.frameRateProvider.singleFrameMs
}
}
@@ -16,6 +16,8 @@
package com.android.launcher3.dagger
import com.android.launcher3.util.window.RefreshRateTracker
import com.android.launcher3.util.window.RefreshRateTracker.RefreshRateTrackerImpl
import com.android.launcher3.widget.LauncherWidgetHolder.WidgetHolderFactory
import com.android.launcher3.widget.LauncherWidgetHolder.WidgetHolderFactoryImpl
import dagger.Binds
@@ -35,7 +37,10 @@ abstract class WidgetModule {
@Module abstract class PluginManagerWrapperModule {}
@Module object StaticObjectModule {}
@Module
abstract class StaticObjectModule {
@Binds abstract fun bindRefreshRateTracker(tracker: RefreshRateTrackerImpl): RefreshRateTracker
}
// Module containing bindings for the final derivative app
@Module abstract class AppModule {}