diff --git a/src/android/app/src/main/java/org/citra/citra_emu/activities/EmulationActivity.kt b/src/android/app/src/main/java/org/citra/citra_emu/activities/EmulationActivity.kt index f23147dd8..d060c59bb 100644 --- a/src/android/app/src/main/java/org/citra/citra_emu/activities/EmulationActivity.kt +++ b/src/android/app/src/main/java/org/citra/citra_emu/activities/EmulationActivity.kt @@ -10,6 +10,7 @@ import android.content.Intent import android.content.SharedPreferences import android.content.pm.PackageManager import android.net.Uri +import android.os.Build import android.os.Bundle import android.view.InputDevice import android.view.KeyEvent @@ -47,6 +48,7 @@ import org.citra.citra_emu.utils.FileBrowserHelper import org.citra.citra_emu.utils.EmulationLifecycleUtil import org.citra.citra_emu.utils.EmulationMenuSettings import org.citra.citra_emu.utils.Log +import org.citra.citra_emu.utils.RefreshRateUtil import org.citra.citra_emu.utils.ThemeUtil import org.citra.citra_emu.viewmodel.EmulationViewModel @@ -82,6 +84,8 @@ class EmulationActivity : AppCompatActivity() { override fun onCreate(savedInstanceState: Bundle?) { requestWindowFeature(Window.FEATURE_NO_TITLE) + RefreshRateUtil.enforceRefreshRate(this, sixtyHz = true) + ThemeUtil.setTheme(this) settingsViewModel.settings.loadSettings() super.onCreate(savedInstanceState) diff --git a/src/android/app/src/main/java/org/citra/citra_emu/features/settings/ui/SettingsActivity.kt b/src/android/app/src/main/java/org/citra/citra_emu/features/settings/ui/SettingsActivity.kt index 070a8f487..064fa700e 100644 --- a/src/android/app/src/main/java/org/citra/citra_emu/features/settings/ui/SettingsActivity.kt +++ b/src/android/app/src/main/java/org/citra/citra_emu/features/settings/ui/SettingsActivity.kt @@ -7,6 +7,7 @@ package org.citra.citra_emu.features.settings.ui import android.content.Context import android.content.Intent import android.net.Uri +import android.os.Build import android.os.Bundle import android.view.View import android.view.ViewGroup.MarginLayoutParams @@ -37,6 +38,7 @@ import org.citra.citra_emu.features.settings.utils.SettingsFile import org.citra.citra_emu.utils.SystemSaveGame import org.citra.citra_emu.utils.DirectoryInitialization import org.citra.citra_emu.utils.InsetsHelper +import org.citra.citra_emu.utils.RefreshRateUtil import org.citra.citra_emu.utils.ThemeUtil class SettingsActivity : AppCompatActivity(), SettingsActivityView { @@ -49,6 +51,8 @@ class SettingsActivity : AppCompatActivity(), SettingsActivityView { override val settings: Settings get() = settingsViewModel.settings override fun onCreate(savedInstanceState: Bundle?) { + RefreshRateUtil.enforceRefreshRate(this) + ThemeUtil.setTheme(this) super.onCreate(savedInstanceState) diff --git a/src/android/app/src/main/java/org/citra/citra_emu/ui/main/MainActivity.kt b/src/android/app/src/main/java/org/citra/citra_emu/ui/main/MainActivity.kt index 37bff3396..71e07225b 100644 --- a/src/android/app/src/main/java/org/citra/citra_emu/ui/main/MainActivity.kt +++ b/src/android/app/src/main/java/org/citra/citra_emu/ui/main/MainActivity.kt @@ -6,6 +6,7 @@ package org.citra.citra_emu.ui.main import android.content.Intent import android.net.Uri +import android.os.Build import android.os.Bundle import android.view.View import android.view.ViewGroup.MarginLayoutParams @@ -51,6 +52,7 @@ import org.citra.citra_emu.utils.CitraDirectoryUtils import org.citra.citra_emu.utils.DirectoryInitialization import org.citra.citra_emu.utils.FileBrowserHelper import org.citra.citra_emu.utils.InsetsHelper +import org.citra.citra_emu.utils.RefreshRateUtil import org.citra.citra_emu.utils.PermissionsHandler import org.citra.citra_emu.utils.ThemeUtil import org.citra.citra_emu.viewmodel.GamesViewModel @@ -66,6 +68,8 @@ class MainActivity : AppCompatActivity(), ThemeProvider { override var themeId: Int = 0 override fun onCreate(savedInstanceState: Bundle?) { + RefreshRateUtil.enforceRefreshRate(this) + val splashScreen = installSplashScreen() CitraDirectoryUtils.attemptAutomaticUpdateDirectory() splashScreen.setKeepOnScreenCondition { diff --git a/src/android/app/src/main/java/org/citra/citra_emu/utils/RefreshRateUtil.kt b/src/android/app/src/main/java/org/citra/citra_emu/utils/RefreshRateUtil.kt new file mode 100644 index 000000000..675fe6702 --- /dev/null +++ b/src/android/app/src/main/java/org/citra/citra_emu/utils/RefreshRateUtil.kt @@ -0,0 +1,53 @@ +// Copyright Citra Emulator Project / Azahar Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +package org.citra.citra_emu.utils +import android.app.Activity +import android.os.Build +import androidx.annotation.RequiresApi + +object RefreshRateUtil { + // Since Android 15, the OS automatically runs apps categorized as games with a + // 60hz refresh rate by default, regardless of the refresh rate set by the user. + // + // This function sets the refresh rate to either the maximum allowed refresh rate or + // 60hz depending on the value of the `sixtyHz` parameter. + // + // Note: This isn't always the maximum refresh rate that the display is *capable of*, + // but is instead the refresh rate chosen by the user in the Android system settings. + // For example, if the user selected 120hz in the settings, but the display is capable + // of 144hz, 120hz will be treated as the maximum within this function. + fun enforceRefreshRate(activity: Activity, sixtyHz: Boolean = false) { + if (Build.VERSION.SDK_INT < Build.VERSION_CODES.R) { + return + } + + val display = activity.display + val window = activity.window + + display?.let { + // Get all supported modes and find the one with the highest refresh rate + val supportedModes = it.supportedModes + val maxRefreshRate = supportedModes.maxByOrNull { mode -> mode.refreshRate } + + if (maxRefreshRate == null) { + return + } + + var newModeId: Int? + if (sixtyHz) { + newModeId = supportedModes.firstOrNull { mode -> mode.refreshRate == 60f }?.modeId + } else { + // Set the preferred display mode to the one with the highest refresh rate + newModeId = maxRefreshRate.modeId + } + + if (newModeId == null) { + return + } + + window.attributes.preferredDisplayModeId = newModeId + } + } +} \ No newline at end of file