mirror of
https://github.com/yairm210/Unciv.git
synced 2025-07-14 17:59:11 +07:00
CrashScreen info improved, allow easier testing of CrashScreen (#10485)
* Drop AndroidTvLauncher - unused * Additional memory info on Android CrashScreen * Refactor getModsAndBaseRuleset - reduce clones and put base first * Add Permanent audiovisual Mods to CrashScreen report * Add a secret Debug option to intentionally crash Unciv * Make "Secret" debug OptionsPopup page available on Android w/o physical keyboard * Add AndroidTvLauncher back in --------- Co-authored-by: Yair Morgenstern <yairm210@hotmail.com>
This commit is contained in:
@ -60,8 +60,8 @@
|
||||
<action android:name="android.intent.action.MAIN" />
|
||||
<category android:name="android.intent.category.LEANBACK_LAUNCHER" />
|
||||
</intent-filter>
|
||||
|
||||
</activity>
|
||||
|
||||
<receiver android:name=".CopyToClipboardReceiver" android:exported="false" />
|
||||
</application>
|
||||
|
||||
|
@ -20,7 +20,7 @@ open class AndroidLauncher : AndroidApplication() {
|
||||
super.onCreate(savedInstanceState)
|
||||
|
||||
// Setup Android logging
|
||||
Log.backend = AndroidLogBackend()
|
||||
Log.backend = AndroidLogBackend(this)
|
||||
|
||||
// Setup Android display
|
||||
Display.platform = AndroidDisplay(this)
|
||||
@ -109,5 +109,3 @@ open class AndroidLauncher : AndroidApplication() {
|
||||
super.onActivityResult(requestCode, resultCode, data)
|
||||
}
|
||||
}
|
||||
|
||||
class AndroidTvLauncher:AndroidLauncher()
|
||||
|
@ -1,5 +1,8 @@
|
||||
package com.unciv.app
|
||||
|
||||
import android.app.Activity
|
||||
import android.app.ActivityManager
|
||||
import android.content.Context
|
||||
import android.os.Build
|
||||
import android.util.Log
|
||||
import com.unciv.utils.LogBackend
|
||||
@ -7,7 +10,14 @@ import com.unciv.utils.Tag
|
||||
|
||||
private const val TAG_MAX_LENGTH = 23
|
||||
|
||||
class AndroidLogBackend : LogBackend {
|
||||
/**
|
||||
* Unciv's logger implementation for Android
|
||||
*
|
||||
* * Note: Gets and keeps a reference to [AndroidLauncher] as [activity] only to get memory info for [CrashScreen][com.unciv.ui.crashhandling.CrashScreen].
|
||||
*
|
||||
* @see com.unciv.utils.Log
|
||||
*/
|
||||
class AndroidLogBackend(private val activity: Activity) : LogBackend {
|
||||
|
||||
override fun debug(tag: Tag, curThreadName: String, msg: String) {
|
||||
Log.d(toAndroidTag(tag), "[$curThreadName] $msg")
|
||||
@ -21,12 +31,29 @@ class AndroidLogBackend : LogBackend {
|
||||
return !BuildConfig.DEBUG
|
||||
}
|
||||
|
||||
/**
|
||||
* @see com.unciv.app.desktop.SystemUtils.getSystemInfo
|
||||
*/
|
||||
override fun getSystemInfo(): String {
|
||||
val memoryInfo = getMemoryInfo()
|
||||
val javaRuntime = Runtime.getRuntime()
|
||||
return """
|
||||
Device Model: ${Build.MODEL}
|
||||
API Level: ${Build.VERSION.SDK_INT}
|
||||
System Memory: ${memoryInfo.totalMem.formatMB()}
|
||||
Available (used by Kernel): ${memoryInfo.availMem.formatMB()}
|
||||
System Low Memory state: ${memoryInfo.lowMemory}
|
||||
Java heap limit: ${javaRuntime.maxMemory().formatMB()}
|
||||
Java heap free: ${javaRuntime.freeMemory().formatMB()}
|
||||
""".trimIndent()
|
||||
}
|
||||
|
||||
private fun getMemoryInfo() = ActivityManager.MemoryInfo().apply {
|
||||
val activityManager = activity.getSystemService(Context.ACTIVITY_SERVICE) as ActivityManager
|
||||
activityManager.getMemoryInfo(this) // API writes into a structure we must supply
|
||||
}
|
||||
|
||||
private fun Long.formatMB() = "${(this + 524288L) / 1048576L} MB"
|
||||
}
|
||||
|
||||
private fun toAndroidTag(tag: Tag): String {
|
||||
|
6
android/src/com/unciv/app/AndroidTvLauncher.kt
Normal file
6
android/src/com/unciv/app/AndroidTvLauncher.kt
Normal file
@ -0,0 +1,6 @@
|
||||
package com.unciv.app
|
||||
|
||||
/**
|
||||
* Proxy without functionality, referenced in AndroidManifest.xml for intent.category.LEANBACK_LAUNCHER
|
||||
*/
|
||||
class AndroidTvLauncher : AndroidLauncher()
|
@ -105,7 +105,14 @@ class GameParameters : IsPartOfGameInfoSerialization { // Default values are the
|
||||
yield(if (mods.isEmpty()) "no mods" else mods.joinToString(",", "mods=(", ")", 6) )
|
||||
}.joinToString(prefix = "(", postfix = ")")
|
||||
|
||||
fun getModsAndBaseRuleset(): HashSet<String> {
|
||||
return mods.toHashSet().apply { add(baseRuleset) }
|
||||
/** Get all mods including base
|
||||
*
|
||||
* The returned Set is ordered base first, then in the order they are stored in a save.
|
||||
* This creates a fresh instance, and the caller is allowed to mutate it.
|
||||
*/
|
||||
fun getModsAndBaseRuleset() =
|
||||
LinkedHashSet<String>(mods.size + 1).apply {
|
||||
add(baseRuleset)
|
||||
addAll(mods)
|
||||
}
|
||||
}
|
||||
|
@ -98,7 +98,6 @@ fun Actor.onRightClick(sound: UncivSound = UncivSound.Click, action: ActivationA
|
||||
* A [sound] will be played (concurrently) on activation unless you specify [UncivSound.Silent].
|
||||
* @return `this` to allow chaining
|
||||
*/
|
||||
@Suppress("unused") // Just in case - for now, the Longpress in WorldMapHolder is using onActivation directly
|
||||
fun Actor.onLongPress(sound: UncivSound = UncivSound.Click, action: ActivationAction): Actor =
|
||||
onActivation(ActivationTypes.Longpress, sound, noEquivalence = true, action)
|
||||
|
||||
|
@ -10,11 +10,11 @@ import com.unciv.Constants
|
||||
import com.unciv.UncivGame
|
||||
import com.unciv.logic.files.UncivFiles
|
||||
import com.unciv.models.ruleset.RulesetCache
|
||||
import com.unciv.ui.components.widgets.AutoScrollPane
|
||||
import com.unciv.ui.components.extensions.addBorder
|
||||
import com.unciv.ui.components.input.onClick
|
||||
import com.unciv.ui.components.extensions.setFontSize
|
||||
import com.unciv.ui.components.extensions.toLabel
|
||||
import com.unciv.ui.components.input.onClick
|
||||
import com.unciv.ui.components.widgets.AutoScrollPane
|
||||
import com.unciv.ui.images.IconTextButton
|
||||
import com.unciv.ui.images.ImageGetter
|
||||
import com.unciv.ui.popups.ToastPopup
|
||||
@ -24,6 +24,8 @@ import java.io.PrintWriter
|
||||
import java.io.StringWriter
|
||||
|
||||
/** Screen to crash to when an otherwise unhandled exception or error is thrown. */
|
||||
//todo We may be in a critical low-memory situation. Using a lot ot String concatenation and trimIndent
|
||||
// could make the display fail when a more efficient StringBuilder approach might still succeed.
|
||||
class CrashScreen(val exception: Throwable) : BaseScreen() {
|
||||
|
||||
private companion object {
|
||||
@ -59,14 +61,23 @@ class CrashScreen(val exception: Throwable) : BaseScreen() {
|
||||
|
||||
/** @return Mods from the last active save game if any, or an informational note otherwise. */
|
||||
private fun tryGetSaveMods(): String {
|
||||
if (!UncivGame.isCurrentInitialized() || UncivGame.Current.gameInfo == null)
|
||||
return ""
|
||||
return "\n**Save Mods:**\n```\n" +
|
||||
if (!UncivGame.isCurrentInitialized()) return ""
|
||||
val game = UncivGame.Current.gameInfo ?: return ""
|
||||
val sb = StringBuilder(160) // capacity: Just some guess
|
||||
sb.append("\n**Save Mods:**\n```\n")
|
||||
try { // Also from old CrashController().buildReport(), also could still error at .toString().
|
||||
LinkedHashSet(UncivGame.Current.gameInfo!!.gameParameters.getModsAndBaseRuleset()).toString()
|
||||
sb.append(game.gameParameters.getModsAndBaseRuleset().toString())
|
||||
} catch (e: Throwable) {
|
||||
"No mod data: $e"
|
||||
} + "\n```\n"
|
||||
sb.append("No mod data: $e")
|
||||
}
|
||||
sb.append("\n```\n")
|
||||
val visualMods = UncivGame.Current.settings.visualMods
|
||||
if (visualMods.isEmpty())
|
||||
return sb.toString()
|
||||
sb.append("**Permanent audiovisual Mods**:\n```\n")
|
||||
sb.append(visualMods.toString())
|
||||
sb.append("\n```\n")
|
||||
return sb.toString()
|
||||
}
|
||||
|
||||
|
||||
|
@ -1,18 +1,21 @@
|
||||
package com.unciv.ui.popups.options
|
||||
|
||||
import com.badlogic.gdx.scenes.scene2d.ui.Table
|
||||
import com.badlogic.gdx.scenes.scene2d.ui.TextButton.TextButtonStyle
|
||||
import com.unciv.GUI
|
||||
import com.unciv.UncivGame
|
||||
import com.unciv.logic.UncivShowableException
|
||||
import com.unciv.logic.files.MapSaver
|
||||
import com.unciv.logic.files.UncivFiles
|
||||
import com.unciv.models.ruleset.RulesetCache
|
||||
import com.unciv.models.ruleset.tile.ResourceType
|
||||
import com.unciv.ui.components.widgets.UncivSlider
|
||||
import com.unciv.ui.components.UncivTextField
|
||||
import com.unciv.ui.components.extensions.addSeparator
|
||||
import com.unciv.ui.components.extensions.toCheckBox
|
||||
import com.unciv.ui.components.extensions.toLabel
|
||||
import com.unciv.ui.components.extensions.toTextButton
|
||||
import com.unciv.ui.components.input.onClick
|
||||
import com.unciv.ui.components.widgets.UncivSlider
|
||||
import com.unciv.ui.screens.basescreen.BaseScreen
|
||||
import com.unciv.utils.DebugUtils
|
||||
|
||||
@ -121,4 +124,10 @@ fun debugTab(
|
||||
GUI.setUpdateWorldOnNextRender()
|
||||
}
|
||||
add(giveResourcesButton).colspan(2).row()
|
||||
|
||||
addSeparator()
|
||||
add("* Crash Unciv! *".toTextButton(skin.get("negative", TextButtonStyle::class.java)).onClick {
|
||||
throw UncivShowableException("Intentional crash")
|
||||
}).colspan(2).row()
|
||||
addSeparator()
|
||||
}
|
||||
|
@ -7,10 +7,10 @@ import com.unciv.GUI
|
||||
import com.unciv.UncivGame
|
||||
import com.unciv.models.metadata.BaseRuleset
|
||||
import com.unciv.models.ruleset.RulesetCache
|
||||
import com.unciv.ui.components.widgets.TabbedPager
|
||||
import com.unciv.ui.components.extensions.areSecretKeysPressed
|
||||
import com.unciv.ui.components.extensions.center
|
||||
import com.unciv.ui.components.extensions.toCheckBox
|
||||
import com.unciv.ui.components.widgets.TabbedPager
|
||||
import com.unciv.ui.images.ImageGetter
|
||||
import com.unciv.ui.popups.Popup
|
||||
import com.unciv.ui.screens.basescreen.BaseScreen
|
||||
@ -28,6 +28,7 @@ import kotlin.reflect.KMutableProperty0
|
||||
class OptionsPopup(
|
||||
screen: BaseScreen,
|
||||
private val selectPage: Int = defaultPage,
|
||||
withDebug: Boolean = false,
|
||||
private val onClose: () -> Unit = {}
|
||||
) : Popup(screen.stage, /** [TabbedPager] handles scrolling */ scrollable = Scrollability.None) {
|
||||
|
||||
@ -110,7 +111,7 @@ class OptionsPopup(
|
||||
val content = ModCheckTab(screen)
|
||||
tabs.addPage("Locate mod errors", content, ImageGetter.getImage("OtherIcons/Mods"), 24f)
|
||||
}
|
||||
if (Gdx.input.areSecretKeysPressed()) {
|
||||
if (withDebug || Gdx.input.areSecretKeysPressed()) {
|
||||
tabs.addPage("Debug", debugTab(this), ImageGetter.getImage("OtherIcons/SecretOptions"), 24f, secret = true)
|
||||
}
|
||||
|
||||
|
@ -174,8 +174,8 @@ abstract class BaseScreen : Screen {
|
||||
/** @return `true` if the screen is narrower than 4:3 landscape */
|
||||
fun isNarrowerThan4to3() = stage.isNarrowerThan4to3()
|
||||
|
||||
open fun openOptionsPopup(startingPage: Int = OptionsPopup.defaultPage, onClose: () -> Unit = {}) {
|
||||
OptionsPopup(this, startingPage, onClose).open(force = true)
|
||||
open fun openOptionsPopup(startingPage: Int = OptionsPopup.defaultPage, withDebug: Boolean = false, onClose: () -> Unit = {}) {
|
||||
OptionsPopup(this, startingPage, withDebug, onClose).open(force = true)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -22,7 +22,6 @@ import com.unciv.models.metadata.GameSetupInfo
|
||||
import com.unciv.models.ruleset.Ruleset
|
||||
import com.unciv.models.ruleset.RulesetCache
|
||||
import com.unciv.models.tilesets.TileSetCache
|
||||
import com.unciv.ui.components.widgets.AutoScrollPane
|
||||
import com.unciv.ui.components.UncivTooltip.Companion.addTooltip
|
||||
import com.unciv.ui.components.extensions.center
|
||||
import com.unciv.ui.components.extensions.surroundWithCircle
|
||||
@ -32,7 +31,9 @@ import com.unciv.ui.components.input.KeyShortcutDispatcherVeto
|
||||
import com.unciv.ui.components.input.KeyboardBinding
|
||||
import com.unciv.ui.components.input.keyShortcuts
|
||||
import com.unciv.ui.components.input.onActivation
|
||||
import com.unciv.ui.components.input.onLongPress
|
||||
import com.unciv.ui.components.tilegroups.TileGroupMap
|
||||
import com.unciv.ui.components.widgets.AutoScrollPane
|
||||
import com.unciv.ui.images.ImageGetter
|
||||
import com.unciv.ui.popups.Popup
|
||||
import com.unciv.ui.popups.ToastPopup
|
||||
@ -45,9 +46,9 @@ import com.unciv.ui.screens.civilopediascreen.CivilopediaScreen
|
||||
import com.unciv.ui.screens.mainmenuscreen.EasterEggRulesets.modifyForEasterEgg
|
||||
import com.unciv.ui.screens.mapeditorscreen.EditorMapHolder
|
||||
import com.unciv.ui.screens.mapeditorscreen.MapEditorScreen
|
||||
import com.unciv.ui.screens.modmanager.ModManagementScreen
|
||||
import com.unciv.ui.screens.multiplayerscreens.MultiplayerScreen
|
||||
import com.unciv.ui.screens.newgamescreen.NewGameScreen
|
||||
import com.unciv.ui.screens.modmanager.ModManagementScreen
|
||||
import com.unciv.ui.screens.savescreens.LoadGameScreen
|
||||
import com.unciv.ui.screens.savescreens.QuickSave
|
||||
import com.unciv.ui.screens.worldscreen.BackgroundActor
|
||||
@ -164,7 +165,8 @@ class MainMenuScreen: BaseScreen(), RecreateOnResize {
|
||||
column2.add(modsTable).row()
|
||||
|
||||
val optionsTable = getMenuButton("Options", "OtherIcons/Options", KeyboardBinding.MainMenuOptions)
|
||||
{ this.openOptionsPopup() }
|
||||
{ openOptionsPopup() }
|
||||
optionsTable.onLongPress { openOptionsPopup(withDebug = true) }
|
||||
column2.add(optionsTable).row()
|
||||
|
||||
|
||||
|
@ -17,10 +17,6 @@ import com.unciv.models.ruleset.unique.UniqueType
|
||||
import com.unciv.models.translations.tr
|
||||
import com.unciv.ui.audio.MusicMood
|
||||
import com.unciv.ui.audio.MusicTrackChooserFlags
|
||||
import com.unciv.ui.components.widgets.AutoScrollPane
|
||||
import com.unciv.ui.components.widgets.ExpanderTab
|
||||
import com.unciv.ui.components.widgets.TranslatedSelectBox
|
||||
import com.unciv.ui.components.widgets.UncivSlider
|
||||
import com.unciv.ui.components.extensions.pad
|
||||
import com.unciv.ui.components.extensions.toCheckBox
|
||||
import com.unciv.ui.components.extensions.toImageButton
|
||||
@ -31,6 +27,10 @@ import com.unciv.ui.components.input.keyShortcuts
|
||||
import com.unciv.ui.components.input.onActivation
|
||||
import com.unciv.ui.components.input.onChange
|
||||
import com.unciv.ui.components.input.onClick
|
||||
import com.unciv.ui.components.widgets.AutoScrollPane
|
||||
import com.unciv.ui.components.widgets.ExpanderTab
|
||||
import com.unciv.ui.components.widgets.TranslatedSelectBox
|
||||
import com.unciv.ui.components.widgets.UncivSlider
|
||||
import com.unciv.ui.images.ImageGetter
|
||||
import com.unciv.ui.popups.Popup
|
||||
import com.unciv.ui.screens.basescreen.BaseScreen
|
||||
@ -458,7 +458,7 @@ class GameOptionsTable(
|
||||
}
|
||||
|
||||
private fun onChooseMod(mod: String) {
|
||||
val activeMods: LinkedHashSet<String> = LinkedHashSet(gameParameters.getModsAndBaseRuleset())
|
||||
val activeMods = gameParameters.getModsAndBaseRuleset()
|
||||
UncivGame.Current.translations.translationActiveMods = activeMods
|
||||
reloadRuleset()
|
||||
update()
|
||||
|
@ -276,10 +276,10 @@ class WorldScreen(
|
||||
}
|
||||
|
||||
// Handle disabling and re-enabling WASD listener while Options are open
|
||||
override fun openOptionsPopup(startingPage: Int, onClose: () -> Unit) {
|
||||
override fun openOptionsPopup(startingPage: Int, withDebug: Boolean, onClose: () -> Unit) {
|
||||
val oldListener = stage.root.listeners.filterIsInstance<KeyboardPanningListener>().firstOrNull()
|
||||
if (oldListener != null) stage.removeListener(oldListener)
|
||||
super.openOptionsPopup(startingPage) {
|
||||
super.openOptionsPopup(startingPage, withDebug) {
|
||||
addKeyboardListener()
|
||||
onClose()
|
||||
}
|
||||
|
@ -1,6 +1,7 @@
|
||||
package com.unciv.ui.screens.worldscreen.mainmenu
|
||||
|
||||
import com.unciv.ui.components.input.KeyboardBinding
|
||||
import com.unciv.ui.components.input.onLongPress
|
||||
import com.unciv.ui.popups.Popup
|
||||
import com.unciv.ui.screens.civilopediascreen.CivilopediaScreen
|
||||
import com.unciv.ui.screens.savescreens.LoadGameScreen
|
||||
@ -35,10 +36,15 @@ class WorldScreenMenuPopup(val worldScreen: WorldScreen) : Popup(worldScreen, sc
|
||||
close()
|
||||
worldScreen.game.pushScreen(VictoryScreen(worldScreen))
|
||||
}.row()
|
||||
addButton("Options", KeyboardBinding.Options) {
|
||||
val optionsCell = addButton("Options", KeyboardBinding.Options) {
|
||||
close()
|
||||
worldScreen.openOptionsPopup()
|
||||
}.row()
|
||||
}
|
||||
optionsCell.actor.onLongPress {
|
||||
close()
|
||||
worldScreen.openOptionsPopup(withDebug = true)
|
||||
}
|
||||
optionsCell.row()
|
||||
addButton("Community") {
|
||||
close()
|
||||
WorldScreenCommunityPopup(worldScreen).open(force = true)
|
||||
|
Reference in New Issue
Block a user