summaryrefslogtreecommitdiffstats
path: root/src/android
diff options
context:
space:
mode:
Diffstat (limited to 'src/android')
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/activities/EmulationActivity.kt17
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/LongMessageDialogFragment.kt62
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/ui/main/MainActivity.kt118
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/MemoryUtil.kt59
-rw-r--r--src/android/app/src/main/jni/native.cpp1
-rw-r--r--src/android/app/src/main/res/values/strings.xml24
6 files changed, 240 insertions, 41 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 2b63388cc..ae665ed2e 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
@@ -27,13 +27,13 @@ import android.view.MotionEvent
import android.view.Surface
import android.view.View
import android.view.inputmethod.InputMethodManager
+import android.widget.Toast
import androidx.activity.viewModels
import androidx.appcompat.app.AppCompatActivity
import androidx.core.view.WindowCompat
import androidx.core.view.WindowInsetsCompat
import androidx.core.view.WindowInsetsControllerCompat
import androidx.navigation.fragment.NavHostFragment
-import kotlin.math.roundToInt
import org.yuzu.yuzu_emu.NativeLibrary
import org.yuzu.yuzu_emu.R
import org.yuzu.yuzu_emu.databinding.ActivityEmulationBinding
@@ -44,8 +44,10 @@ import org.yuzu.yuzu_emu.model.Game
import org.yuzu.yuzu_emu.utils.ControllerMappingHelper
import org.yuzu.yuzu_emu.utils.ForegroundService
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 kotlin.math.roundToInt
class EmulationActivity : AppCompatActivity(), SensorEventListener {
private lateinit var binding: ActivityEmulationBinding
@@ -104,6 +106,19 @@ class EmulationActivity : AppCompatActivity(), SensorEventListener {
inputHandler = InputHandler()
inputHandler.initialize()
+ val memoryUtil = MemoryUtil(this)
+ if (memoryUtil.isLessThan(8, MemoryUtil.Gb)) {
+ Toast.makeText(
+ this,
+ getString(
+ R.string.device_memory_inadequate,
+ memoryUtil.getDeviceRAM(),
+ "8 ${getString(R.string.memory_gigabyte)}"
+ ),
+ Toast.LENGTH_LONG
+ ).show()
+ }
+
// Start a foreground service to prevent the app from getting killed in the background
val startIntent = Intent(this, ForegroundService::class.java)
startForegroundService(startIntent)
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/LongMessageDialogFragment.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/LongMessageDialogFragment.kt
new file mode 100644
index 000000000..b29b627e9
--- /dev/null
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/LongMessageDialogFragment.kt
@@ -0,0 +1,62 @@
+// SPDX-FileCopyrightText: 2023 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+package org.yuzu.yuzu_emu.fragments
+
+import android.app.Dialog
+import android.content.Intent
+import android.net.Uri
+import android.os.Bundle
+import androidx.fragment.app.DialogFragment
+import com.google.android.material.dialog.MaterialAlertDialogBuilder
+import org.yuzu.yuzu_emu.R
+
+class LongMessageDialogFragment : DialogFragment() {
+ override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
+ val titleId = requireArguments().getInt(TITLE)
+ val description = requireArguments().getString(DESCRIPTION)
+ val helpLinkId = requireArguments().getInt(HELP_LINK)
+
+ val dialog = MaterialAlertDialogBuilder(requireContext())
+ .setPositiveButton(R.string.close, null)
+ .setTitle(titleId)
+ .setMessage(description)
+
+ if (helpLinkId != 0) {
+ dialog.setNeutralButton(R.string.learn_more) { _, _ ->
+ openLink(getString(helpLinkId))
+ }
+ }
+
+ return dialog.show()
+ }
+
+ private fun openLink(link: String) {
+ val intent = Intent(Intent.ACTION_VIEW, Uri.parse(link))
+ startActivity(intent)
+ }
+
+ companion object {
+ const val TAG = "LongMessageDialogFragment"
+
+ private const val TITLE = "Title"
+ private const val DESCRIPTION = "Description"
+ private const val HELP_LINK = "Link"
+
+ fun newInstance(
+ titleId: Int,
+ description: String,
+ helpLinkId: Int = 0
+ ): LongMessageDialogFragment {
+ val dialog = LongMessageDialogFragment()
+ val bundle = Bundle()
+ bundle.apply {
+ putInt(TITLE, titleId)
+ putString(DESCRIPTION, description)
+ putInt(HELP_LINK, helpLinkId)
+ }
+ dialog.arguments = bundle
+ return dialog
+ }
+ }
+}
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/ui/main/MainActivity.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/ui/main/MainActivity.kt
index cc1d87f1b..3086cfad3 100644
--- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/ui/main/MainActivity.kt
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/ui/main/MainActivity.kt
@@ -4,6 +4,7 @@
package org.yuzu.yuzu_emu.ui.main
import android.content.Intent
+import android.net.Uri
import android.os.Bundle
import android.view.View
import android.view.ViewGroup.MarginLayoutParams
@@ -42,6 +43,7 @@ import org.yuzu.yuzu_emu.features.settings.model.SettingsViewModel
import org.yuzu.yuzu_emu.features.settings.ui.SettingsActivity
import org.yuzu.yuzu_emu.features.settings.utils.SettingsFile
import org.yuzu.yuzu_emu.fragments.IndeterminateProgressDialogFragment
+import org.yuzu.yuzu_emu.fragments.LongMessageDialogFragment
import org.yuzu.yuzu_emu.fragments.MessageDialogFragment
import org.yuzu.yuzu_emu.model.GamesViewModel
import org.yuzu.yuzu_emu.model.HomeViewModel
@@ -481,62 +483,110 @@ class MainActivity : AppCompatActivity(), ThemeProvider {
}
}
- val installGameUpdate =
- registerForActivityResult(ActivityResultContracts.OpenDocument()) {
- if (it == null) {
- return@registerForActivityResult
- }
-
+ val installGameUpdate = registerForActivityResult(
+ ActivityResultContracts.OpenMultipleDocuments()
+ ) { documents: List<Uri> ->
+ if (documents.isNotEmpty()) {
IndeterminateProgressDialogFragment.newInstance(
this@MainActivity,
R.string.install_game_content
) {
- val result = NativeLibrary.installFileToNand(it.toString())
+ var installSuccess = 0
+ var installOverwrite = 0
+ var errorBaseGame = 0
+ var errorExtension = 0
+ var errorOther = 0
+ var errorTotal = 0
lifecycleScope.launch {
- withContext(Dispatchers.Main) {
- when (result) {
+ documents.forEach {
+ when (NativeLibrary.installFileToNand(it.toString())) {
NativeLibrary.InstallFileToNandResult.Success -> {
- Toast.makeText(
- applicationContext,
- R.string.install_game_content_success,
- Toast.LENGTH_SHORT
- ).show()
+ installSuccess += 1
}
NativeLibrary.InstallFileToNandResult.SuccessFileOverwritten -> {
- Toast.makeText(
- applicationContext,
- R.string.install_game_content_success_overwrite,
- Toast.LENGTH_SHORT
- ).show()
+ installOverwrite += 1
}
NativeLibrary.InstallFileToNandResult.ErrorBaseGame -> {
- MessageDialogFragment.newInstance(
- R.string.install_game_content_failure,
- R.string.install_game_content_failure_base
- ).show(supportFragmentManager, MessageDialogFragment.TAG)
+ errorBaseGame += 1
}
NativeLibrary.InstallFileToNandResult.ErrorFilenameExtension -> {
- MessageDialogFragment.newInstance(
- R.string.install_game_content_failure,
- R.string.install_game_content_failure_file_extension,
- R.string.install_game_content_help_link
- ).show(supportFragmentManager, MessageDialogFragment.TAG)
+ errorExtension += 1
}
else -> {
- MessageDialogFragment.newInstance(
- R.string.install_game_content_failure,
- R.string.install_game_content_failure_description,
- R.string.install_game_content_help_link
- ).show(supportFragmentManager, MessageDialogFragment.TAG)
+ errorOther += 1
}
}
}
+ withContext(Dispatchers.Main) {
+ val separator = System.getProperty("line.separator") ?: "\n"
+ val installResult = StringBuilder()
+ if (installSuccess > 0) {
+ installResult.append(
+ getString(
+ R.string.install_game_content_success_install,
+ installSuccess
+ )
+ )
+ installResult.append(separator)
+ }
+ if (installOverwrite > 0) {
+ installResult.append(
+ getString(
+ R.string.install_game_content_success_overwrite,
+ installOverwrite
+ )
+ )
+ installResult.append(separator)
+ }
+ errorTotal = errorBaseGame + errorExtension + errorOther
+ if (errorTotal > 0) {
+ installResult.append(separator)
+ installResult.append(
+ getString(
+ R.string.install_game_content_failed_count,
+ errorTotal
+ )
+ )
+ installResult.append(separator)
+ if (errorBaseGame > 0) {
+ installResult.append(separator)
+ installResult.append(
+ getString(R.string.install_game_content_failure_base)
+ )
+ installResult.append(separator)
+ }
+ if (errorExtension > 0) {
+ installResult.append(separator)
+ installResult.append(
+ getString(R.string.install_game_content_failure_file_extension)
+ )
+ installResult.append(separator)
+ }
+ if (errorOther > 0) {
+ installResult.append(
+ getString(R.string.install_game_content_failure_description)
+ )
+ installResult.append(separator)
+ }
+ LongMessageDialogFragment.newInstance(
+ R.string.install_game_content_failure,
+ installResult.toString().trim(),
+ R.string.install_game_content_help_link
+ ).show(supportFragmentManager, LongMessageDialogFragment.TAG)
+ } else {
+ LongMessageDialogFragment.newInstance(
+ R.string.install_game_content_success,
+ installResult.toString().trim()
+ ).show(supportFragmentManager, LongMessageDialogFragment.TAG)
+ }
+ }
}
- return@newInstance result
+ return@newInstance installSuccess + installOverwrite + errorTotal
}.show(supportFragmentManager, IndeterminateProgressDialogFragment.TAG)
}
+ }
}
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
new file mode 100644
index 000000000..18e5fa0b0
--- /dev/null
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/MemoryUtil.kt
@@ -0,0 +1,59 @@
+// SPDX-FileCopyrightText: 2023 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+package org.yuzu.yuzu_emu.utils
+
+import android.app.ActivityManager
+import android.content.Context
+import org.yuzu.yuzu_emu.R
+import java.util.Locale
+
+class MemoryUtil(val context: Context) {
+
+ private val Long.floatForm: String
+ get() = String.format(Locale.ROOT, "%.2f", this.toDouble())
+
+ 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 val totalMemory =
+ with(context.getSystemService(Context.ACTIVITY_SERVICE) as ActivityManager) {
+ val memInfo = ActivityManager.MemoryInfo()
+ getMemoryInfo(memInfo)
+ memInfo.totalMem
+ }
+
+ fun isLessThan(minimum: Int, size: Long): Boolean {
+ return when (size) {
+ Kb -> totalMemory < Mb && totalMemory < minimum
+ Mb -> totalMemory < Gb && (totalMemory / Mb) < minimum
+ Gb -> totalMemory < Tb && (totalMemory / Gb) < minimum
+ Tb -> totalMemory < Pb && (totalMemory / Tb) < minimum
+ Pb -> totalMemory < Eb && (totalMemory / Pb) < minimum
+ Eb -> totalMemory / Eb < minimum
+ else -> totalMemory < Kb && totalMemory < minimum
+ }
+ }
+
+ 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
+ }
+}
diff --git a/src/android/app/src/main/jni/native.cpp b/src/android/app/src/main/jni/native.cpp
index 6688416d6..d576aac50 100644
--- a/src/android/app/src/main/jni/native.cpp
+++ b/src/android/app/src/main/jni/native.cpp
@@ -238,6 +238,7 @@ public:
m_software_keyboard = android_keyboard.get();
m_system.SetShuttingDown(false);
m_system.ApplySettings();
+ Settings::LogSettings();
m_system.HIDCore().ReloadInputDevices();
m_system.SetAppletFrontendSet({
nullptr, // Amiibo Settings
diff --git a/src/android/app/src/main/res/values/strings.xml b/src/android/app/src/main/res/values/strings.xml
index 381dfbc3b..af7450619 100644
--- a/src/android/app/src/main/res/values/strings.xml
+++ b/src/android/app/src/main/res/values/strings.xml
@@ -104,12 +104,14 @@
<string name="share_log_missing">No log file found</string>
<string name="install_game_content">Install game content</string>
<string name="install_game_content_description">Install game updates or DLC</string>
- <string name="install_game_content_failure">Error installing file to NAND</string>
- <string name="install_game_content_failure_description">Game content installation failed. Please ensure content is valid and that the prod.keys file is installed.</string>
- <string name="install_game_content_failure_base">Installation of base games isn\'t permitted in order to avoid possible conflicts. Please select an update or DLC instead.</string>
- <string name="install_game_content_failure_file_extension">The selected file type is not supported. Only NSP and XCI content is supported for this action. Please verify the game content is valid.</string>
- <string name="install_game_content_success">Game content installed successfully</string>
- <string name="install_game_content_success_overwrite">Game content was overwritten successfully</string>
+ <string name="install_game_content_failure">Error installing file(s) to NAND</string>
+ <string name="install_game_content_failure_description">Please ensure content(s) are valid and that the prod.keys file is installed.</string>
+ <string name="install_game_content_failure_base">Installation of base games isn\'t permitted in order to avoid possible conflicts.</string>
+ <string name="install_game_content_failure_file_extension">Only NSP and XCI content is supported. Please verify the game content(s) are valid.</string>
+ <string name="install_game_content_failed_count">%1$d installation error(s)</string>
+ <string name="install_game_content_success">Game content(s) installed successfully</string>
+ <string name="install_game_content_success_install">%1$d installed successfully</string>
+ <string name="install_game_content_success_overwrite">%1$d overwritten successfully</string>
<string name="install_game_content_help_link">https://yuzu-emu.org/help/quickstart/#dumping-installed-updates</string>
<!-- About screen strings -->
@@ -270,6 +272,7 @@
<string name="fatal_error">Fatal Error</string>
<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>
<!-- Region Names -->
<string name="region_japan">Japan</string>
@@ -300,6 +303,15 @@
<string name="language_traditional_chinese">Traditional Chinese (正體中文)</string>
<string name="language_brazilian_portuguese">Brazilian Portuguese (Português do Brasil)</string>
+ <!-- Memory Sizes -->
+ <string name="memory_byte">Byte</string>
+ <string name="memory_kilobyte">KB</string>
+ <string name="memory_megabyte">MB</string>
+ <string name="memory_gigabyte">GB</string>
+ <string name="memory_terabyte">TB</string>
+ <string name="memory_petabyte">PB</string>
+ <string name="memory_exabyte">EB</string>
+
<!-- Renderer APIs -->
<string name="renderer_vulkan">Vulkan</string>
<string name="renderer_none">None</string>