From a1dd5dfba5ed87242ca6498a5baa8a105ee72a64 Mon Sep 17 00:00:00 2001
From: Charles Lombardo <clombardo169@gmail.com>
Date: Wed, 28 Jun 2023 15:05:32 -0400
Subject: [PATCH 1/3] android: Make MemoryUtil an object

---
 .../yuzu_emu/activities/EmulationActivity.kt  |  5 ++---
 .../org/yuzu/yuzu_emu/utils/MemoryUtil.kt     | 20 +++++++++----------
 2 files changed, 12 insertions(+), 13 deletions(-)

diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/activities/EmulationActivity.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/activities/EmulationActivity.kt
index ae665ed2ea..4052eead50 100644
--- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/activities/EmulationActivity.kt
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/activities/EmulationActivity.kt
@@ -106,13 +106,12 @@ class EmulationActivity : AppCompatActivity(), SensorEventListener {
         inputHandler = InputHandler()
         inputHandler.initialize()
 
-        val memoryUtil = MemoryUtil(this)
-        if (memoryUtil.isLessThan(8, MemoryUtil.Gb)) {
+        if (MemoryUtil.isLessThan(8, MemoryUtil.Gb)) {
             Toast.makeText(
                 this,
                 getString(
                     R.string.device_memory_inadequate,
-                    memoryUtil.getDeviceRAM(),
+                    MemoryUtil.getDeviceRAM(),
                     "8 ${getString(R.string.memory_gigabyte)}"
                 ),
                 Toast.LENGTH_LONG
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/MemoryUtil.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/MemoryUtil.kt
index 18e5fa0b0d..59e9f8f873 100644
--- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/MemoryUtil.kt
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/MemoryUtil.kt
@@ -6,13 +6,22 @@ package org.yuzu.yuzu_emu.utils
 import android.app.ActivityManager
 import android.content.Context
 import org.yuzu.yuzu_emu.R
+import org.yuzu.yuzu_emu.YuzuApplication
 import java.util.Locale
 
-class MemoryUtil(val context: Context) {
+object MemoryUtil {
+    private val context get() = YuzuApplication.appContext
 
     private val Long.floatForm: String
         get() = String.format(Locale.ROOT, "%.2f", this.toDouble())
 
+    const val Kb: Long = 1024
+    const val Mb = Kb * 1024
+    const val Gb = Mb * 1024
+    const val Tb = Gb * 1024
+    const val Pb = Tb * 1024
+    const val Eb = Pb * 1024
+
     private fun bytesToSizeUnit(size: Long): String {
         return when {
             size < Kb -> "${size.floatForm} ${context.getString(R.string.memory_byte)}"
@@ -47,13 +56,4 @@ class MemoryUtil(val context: Context) {
     fun getDeviceRAM(): String {
         return bytesToSizeUnit(totalMemory)
     }
-
-    companion object {
-        const val Kb: Long = 1024
-        const val Mb = Kb * 1024
-        const val Gb = Mb * 1024
-        const val Tb = Gb * 1024
-        const val Pb = Tb * 1024
-        const val Eb = Pb * 1024
-    }
 }

From 11991fbd7f8b97a13bb315e7c035b900052df204 Mon Sep 17 00:00:00 2001
From: Charles Lombardo <clombardo169@gmail.com>
Date: Wed, 28 Jun 2023 16:51:27 -0400
Subject: [PATCH 2/3] android: Rework MemoryUtil

Uses string templates and rounds up memory amount for potentially inaccurate checks now
---
 .../yuzu_emu/activities/EmulationActivity.kt  |   9 +-
 .../org/yuzu/yuzu_emu/utils/MemoryUtil.kt     | 100 ++++++++++++++----
 .../app/src/main/res/values/strings.xml       |   1 +
 3 files changed, 85 insertions(+), 25 deletions(-)

diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/activities/EmulationActivity.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/activities/EmulationActivity.kt
index 4052eead50..2c671fea38 100644
--- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/activities/EmulationActivity.kt
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/activities/EmulationActivity.kt
@@ -47,6 +47,7 @@ import org.yuzu.yuzu_emu.utils.InputHandler
 import org.yuzu.yuzu_emu.utils.MemoryUtil
 import org.yuzu.yuzu_emu.utils.NfcReader
 import org.yuzu.yuzu_emu.utils.ThemeHelper
+import java.text.NumberFormat
 import kotlin.math.roundToInt
 
 class EmulationActivity : AppCompatActivity(), SensorEventListener {
@@ -106,13 +107,17 @@ class EmulationActivity : AppCompatActivity(), SensorEventListener {
         inputHandler = InputHandler()
         inputHandler.initialize()
 
-        if (MemoryUtil.isLessThan(8, MemoryUtil.Gb)) {
+        if (MemoryUtil.isLessThan(MemoryUtil.REQUIRED_MEMORY, MemoryUtil.Gb)) {
             Toast.makeText(
                 this,
                 getString(
                     R.string.device_memory_inadequate,
                     MemoryUtil.getDeviceRAM(),
-                    "8 ${getString(R.string.memory_gigabyte)}"
+                    getString(
+                        R.string.memory_formatted,
+                        NumberFormat.getInstance().format(MemoryUtil.REQUIRED_MEMORY),
+                        getString(R.string.memory_gigabyte)
+                    )
                 ),
                 Toast.LENGTH_LONG
             ).show()
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/MemoryUtil.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/MemoryUtil.kt
index 59e9f8f873..aa4a5539a6 100644
--- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/MemoryUtil.kt
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/MemoryUtil.kt
@@ -5,44 +5,101 @@ package org.yuzu.yuzu_emu.utils
 
 import android.app.ActivityManager
 import android.content.Context
+import android.os.Build
 import org.yuzu.yuzu_emu.R
 import org.yuzu.yuzu_emu.YuzuApplication
 import java.util.Locale
+import kotlin.math.ceil
 
 object MemoryUtil {
     private val context get() = YuzuApplication.appContext
 
-    private val Long.floatForm: String
-        get() = String.format(Locale.ROOT, "%.2f", this.toDouble())
+    private val Float.hundredths: String
+        get() = String.format(Locale.ROOT, "%.2f", this)
 
-    const val Kb: Long = 1024
+    // Required total system memory
+    const val REQUIRED_MEMORY = 8
+
+    const val Kb: Float = 1024F
     const val Mb = Kb * 1024
     const val Gb = Mb * 1024
     const val Tb = Gb * 1024
     const val Pb = Tb * 1024
     const val Eb = Pb * 1024
 
-    private fun bytesToSizeUnit(size: Long): String {
-        return when {
-            size < Kb -> "${size.floatForm} ${context.getString(R.string.memory_byte)}"
-            size < Mb -> "${(size / Kb).floatForm} ${context.getString(R.string.memory_kilobyte)}"
-            size < Gb -> "${(size / Mb).floatForm} ${context.getString(R.string.memory_megabyte)}"
-            size < Tb -> "${(size / Gb).floatForm} ${context.getString(R.string.memory_gigabyte)}"
-            size < Pb -> "${(size / Tb).floatForm} ${context.getString(R.string.memory_terabyte)}"
-            size < Eb -> "${(size / Pb).floatForm} ${context.getString(R.string.memory_petabyte)}"
-            else -> "${(size / Eb).floatForm} ${context.getString(R.string.memory_exabyte)}"
+    private fun bytesToSizeUnit(size: Float): String =
+        when {
+            size < Kb -> {
+                context.getString(
+                    R.string.memory_formatted,
+                    size.hundredths,
+                    context.getString(R.string.memory_byte)
+                )
+            }
+            size < Mb -> {
+                context.getString(
+                    R.string.memory_formatted,
+                    (size / Kb).hundredths,
+                    context.getString(R.string.memory_kilobyte)
+                )
+            }
+            size < Gb -> {
+                context.getString(
+                    R.string.memory_formatted,
+                    (size / Mb).hundredths,
+                    context.getString(R.string.memory_megabyte)
+                )
+            }
+            size < Tb -> {
+                context.getString(
+                    R.string.memory_formatted,
+                    (size / Gb).hundredths,
+                    context.getString(R.string.memory_gigabyte)
+                )
+            }
+            size < Pb -> {
+                context.getString(
+                    R.string.memory_formatted,
+                    (size / Tb).hundredths,
+                    context.getString(R.string.memory_terabyte)
+                )
+            }
+            size < Eb -> {
+                context.getString(
+                    R.string.memory_formatted,
+                    (size / Pb).hundredths,
+                    context.getString(R.string.memory_petabyte)
+                )
+            }
+            else -> {
+                context.getString(
+                    R.string.memory_formatted,
+                    (size / Eb).hundredths,
+                    context.getString(R.string.memory_exabyte)
+                )
+            }
         }
-    }
 
-    private val totalMemory =
-        with(context.getSystemService(Context.ACTIVITY_SERVICE) as ActivityManager) {
+    // Devices are unlikely to have 0.5GB increments of memory so we'll just round up to account for
+    // the potential error created by memInfo.totalMem
+    private val totalMemory: Float
+        get() {
             val memInfo = ActivityManager.MemoryInfo()
-            getMemoryInfo(memInfo)
-            memInfo.totalMem
+            with(context.getSystemService(Context.ACTIVITY_SERVICE) as ActivityManager) {
+                getMemoryInfo(memInfo)
+            }
+
+            return ceil(
+                if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.UPSIDE_DOWN_CAKE) {
+                    memInfo.advertisedMem.toFloat()
+                } else {
+                    memInfo.totalMem.toFloat()
+                }
+            )
         }
 
-    fun isLessThan(minimum: Int, size: Long): Boolean {
-        return when (size) {
+    fun isLessThan(minimum: Int, size: Float): Boolean =
+        when (size) {
             Kb -> totalMemory < Mb && totalMemory < minimum
             Mb -> totalMemory < Gb && (totalMemory / Mb) < minimum
             Gb -> totalMemory < Tb && (totalMemory / Gb) < minimum
@@ -51,9 +108,6 @@ object MemoryUtil {
             Eb -> totalMemory / Eb < minimum
             else -> totalMemory < Kb && totalMemory < minimum
         }
-    }
 
-    fun getDeviceRAM(): String {
-        return bytesToSizeUnit(totalMemory)
-    }
+    fun getDeviceRAM(): String = bytesToSizeUnit(totalMemory)
 }
diff --git a/src/android/app/src/main/res/values/strings.xml b/src/android/app/src/main/res/values/strings.xml
index af7450619f..b3c7379796 100644
--- a/src/android/app/src/main/res/values/strings.xml
+++ b/src/android/app/src/main/res/values/strings.xml
@@ -273,6 +273,7 @@
     <string name="fatal_error_message">A fatal error occurred. Check the log for details.\nContinuing emulation may result in crashes and bugs.</string>
     <string name="performance_warning">Turning off this setting will significantly reduce emulation performance! For the best experience, it is recommended that you leave this setting enabled.</string>
     <string name="device_memory_inadequate">Device RAM: %1$s\nRecommended: %2$s</string>
+    <string name="memory_formatted">%1$s %2$s</string>
 
     <!-- Region Names -->
     <string name="region_japan">Japan</string>

From ff6d35f2c780df5e3604979eef0d5b7c194617ea Mon Sep 17 00:00:00 2001
From: Charles Lombardo <clombardo169@gmail.com>
Date: Fri, 30 Jun 2023 13:46:35 -0400
Subject: [PATCH 3/3] android: Show memory warning once

---
 .../yuzu_emu/activities/EmulationActivity.kt  | 35 ++++++++++++-------
 .../features/settings/model/Settings.kt       |  2 ++
 2 files changed, 24 insertions(+), 13 deletions(-)

diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/activities/EmulationActivity.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/activities/EmulationActivity.kt
index 2c671fea38..7461fb093f 100644
--- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/activities/EmulationActivity.kt
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/activities/EmulationActivity.kt
@@ -34,11 +34,14 @@ import androidx.core.view.WindowCompat
 import androidx.core.view.WindowInsetsCompat
 import androidx.core.view.WindowInsetsControllerCompat
 import androidx.navigation.fragment.NavHostFragment
+import androidx.preference.PreferenceManager
 import org.yuzu.yuzu_emu.NativeLibrary
 import org.yuzu.yuzu_emu.R
+import org.yuzu.yuzu_emu.YuzuApplication
 import org.yuzu.yuzu_emu.databinding.ActivityEmulationBinding
 import org.yuzu.yuzu_emu.features.settings.model.BooleanSetting
 import org.yuzu.yuzu_emu.features.settings.model.IntSetting
+import org.yuzu.yuzu_emu.features.settings.model.Settings
 import org.yuzu.yuzu_emu.features.settings.model.SettingsViewModel
 import org.yuzu.yuzu_emu.model.Game
 import org.yuzu.yuzu_emu.utils.ControllerMappingHelper
@@ -107,20 +110,26 @@ class EmulationActivity : AppCompatActivity(), SensorEventListener {
         inputHandler = InputHandler()
         inputHandler.initialize()
 
-        if (MemoryUtil.isLessThan(MemoryUtil.REQUIRED_MEMORY, MemoryUtil.Gb)) {
-            Toast.makeText(
-                this,
-                getString(
-                    R.string.device_memory_inadequate,
-                    MemoryUtil.getDeviceRAM(),
+        val preferences = PreferenceManager.getDefaultSharedPreferences(YuzuApplication.appContext)
+        if (!preferences.getBoolean(Settings.PREF_MEMORY_WARNING_SHOWN, false)) {
+            if (MemoryUtil.isLessThan(MemoryUtil.REQUIRED_MEMORY, MemoryUtil.Gb)) {
+                Toast.makeText(
+                    this,
                     getString(
-                        R.string.memory_formatted,
-                        NumberFormat.getInstance().format(MemoryUtil.REQUIRED_MEMORY),
-                        getString(R.string.memory_gigabyte)
-                    )
-                ),
-                Toast.LENGTH_LONG
-            ).show()
+                        R.string.device_memory_inadequate,
+                        MemoryUtil.getDeviceRAM(),
+                        getString(
+                            R.string.memory_formatted,
+                            NumberFormat.getInstance().format(MemoryUtil.REQUIRED_MEMORY),
+                            getString(R.string.memory_gigabyte)
+                        )
+                    ),
+                    Toast.LENGTH_LONG
+                ).show()
+                preferences.edit()
+                    .putBoolean(Settings.PREF_MEMORY_WARNING_SHOWN, true)
+                    .apply()
+            }
         }
 
         // Start a foreground service to prevent the app from getting killed in the background
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/Settings.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/Settings.kt
index 88afb2223c..be6e17e65c 100644
--- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/Settings.kt
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/Settings.kt
@@ -110,6 +110,8 @@ class Settings {
         const val SECTION_THEME = "Theme"
         const val SECTION_DEBUG = "Debug"
 
+        const val PREF_MEMORY_WARNING_SHOWN = "MemoryWarningShown"
+
         const val PREF_OVERLAY_INIT = "OverlayInit"
         const val PREF_CONTROL_SCALE = "controlScale"
         const val PREF_CONTROL_OPACITY = "controlOpacity"