Fixed Multiplayer Turn Notifier periodically failing with error notification. (#2054)
This commit is contained in:
parent
10b95c6c1c
commit
1df0c408aa
8 changed files with 96 additions and 29 deletions
|
@ -1,5 +1,6 @@
|
||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
xmlns:tools="http://schemas.android.com/tools"
|
||||||
package="com.unciv.app" >
|
package="com.unciv.app" >
|
||||||
|
|
||||||
<uses-sdk/>
|
<uses-sdk/>
|
||||||
|
@ -17,13 +18,14 @@
|
||||||
android:launchMode="singleTask"
|
android:launchMode="singleTask"
|
||||||
android:label="@string/app_name"
|
android:label="@string/app_name"
|
||||||
android:screenOrientation="userLandscape"
|
android:screenOrientation="userLandscape"
|
||||||
android:configChanges="keyboard|keyboardHidden|orientation|screenSize">
|
android:configChanges="keyboard|keyboardHidden|orientation|screenSize"
|
||||||
|
tools:ignore="LockedOrientationActivity">
|
||||||
<intent-filter>
|
<intent-filter>
|
||||||
<action android:name="android.intent.action.MAIN" />
|
<action android:name="android.intent.action.MAIN" />
|
||||||
<category android:name="android.intent.category.LAUNCHER" />
|
<category android:name="android.intent.category.LAUNCHER" />
|
||||||
</intent-filter>
|
</intent-filter>
|
||||||
|
|
||||||
</activity>
|
</activity>
|
||||||
|
<receiver android:name=".CopyToClipboardReceiver" android:exported="false" />
|
||||||
</application>
|
</application>
|
||||||
|
|
||||||
</manifest>
|
</manifest>
|
||||||
|
|
|
@ -4,8 +4,8 @@
|
||||||
<string name="Notify_YourTurn_Short">Unciv - Du bist am Zug!</string>
|
<string name="Notify_YourTurn_Short">Unciv - Du bist am Zug!</string>
|
||||||
<string name="Notify_YourTurn_Long">Deine Freunde warten auf deinen Zug.</string>
|
<string name="Notify_YourTurn_Long">Deine Freunde warten auf deinen Zug.</string>
|
||||||
<string name="Notify_Error_Short">Ein Fehler ist aufgetreten</string>
|
<string name="Notify_Error_Short">Ein Fehler ist aufgetreten</string>
|
||||||
<string name="Notify_Error_Long">Multiplayer Zug Benachrichtigungsdienst wurde beendet</string>
|
<string name="Notify_Error_Long">Multiplayer Zug Benachrichtigungsdienst wurde beendet.</string>
|
||||||
<string name="Notify_Persist_Short">Letzter online Zug Check:</string>
|
<string name="Notify_Persist_Short">Letzter online Zugcheck:</string>
|
||||||
<string name="Notify_Persist_Long_P1">Unciv wird dich benachrichtigen, wenn du im Multiplayer am Zug bist.</string>
|
<string name="Notify_Persist_Long_P1">Unciv wird dich benachrichtigen, wenn du im Multiplayer am Zug bist.</string>
|
||||||
<string name="Notify_Persist_Long_P2">Prüft etwa alle</string>
|
<string name="Notify_Persist_Long_P2">Prüft etwa alle</string>
|
||||||
<string name="Notify_Persist_Long_P3">Minute(n) wenn Internet vorhanden.</string>
|
<string name="Notify_Persist_Long_P3">Minute(n) wenn Internet vorhanden.</string>
|
||||||
|
@ -14,4 +14,7 @@
|
||||||
<string name="Notify_ChannelInfo_Long">Informiert dich, wenn du im Multiplayer am Zug bist.</string>
|
<string name="Notify_ChannelInfo_Long">Informiert dich, wenn du im Multiplayer am Zug bist.</string>
|
||||||
<string name="Notify_ChannelService_Short">Unciv Multiplayer Zugprüfer Persistenter Status</string>
|
<string name="Notify_ChannelService_Short">Unciv Multiplayer Zugprüfer Persistenter Status</string>
|
||||||
<string name="Notify_ChannelService_Long">Permanent angezeigte Benachrichtigung, welche dich über die Hintergrundaktivität des Dienstes informiert.</string>
|
<string name="Notify_ChannelService_Long">Permanent angezeigte Benachrichtigung, welche dich über die Hintergrundaktivität des Dienstes informiert.</string>
|
||||||
|
<string name="Notify_Error_StackTrace_Toast">Stacktraces in Zwischenablage kopiert.</string>
|
||||||
|
<string name="Notify_Error_CopyAction">Kopiere Stacktraces in Zwischenablage</string>
|
||||||
|
<string name="Notify_Error_Retrying">Fehler, wiederhole…</string>
|
||||||
</resources>
|
</resources>
|
|
@ -4,7 +4,7 @@
|
||||||
<string name="Notify_YourTurn_Short">Unciv - It\'s your turn!</string>
|
<string name="Notify_YourTurn_Short">Unciv - It\'s your turn!</string>
|
||||||
<string name="Notify_YourTurn_Long">Your friends are waiting on your turn.</string>
|
<string name="Notify_YourTurn_Long">Your friends are waiting on your turn.</string>
|
||||||
<string name="Notify_Error_Short">An error has occurred</string>
|
<string name="Notify_Error_Short">An error has occurred</string>
|
||||||
<string name="Notify_Error_Long">Multiplayer turn notifier service terminated</string>
|
<string name="Notify_Error_Long">Multiplayer turn notifier service terminated.</string>
|
||||||
<string name="Notify_Persist_Short">Last online turn check:</string>
|
<string name="Notify_Persist_Short">Last online turn check:</string>
|
||||||
<string name="Notify_Persist_Long_P1">Unciv will inform you when it\'s your turn in multiplayer.</string>
|
<string name="Notify_Persist_Long_P1">Unciv will inform you when it\'s your turn in multiplayer.</string>
|
||||||
<string name="Notify_Persist_Long_P2">Checks ca. every</string>
|
<string name="Notify_Persist_Long_P2">Checks ca. every</string>
|
||||||
|
@ -14,4 +14,7 @@
|
||||||
<string name="Notify_ChannelInfo_Long">Informs you when it\'s your turn in multiplayer.</string>
|
<string name="Notify_ChannelInfo_Long">Informs you when it\'s your turn in multiplayer.</string>
|
||||||
<string name="Notify_ChannelService_Short">Unciv Multiplayer Turn Checker Persistent Status</string>
|
<string name="Notify_ChannelService_Short">Unciv Multiplayer Turn Checker Persistent Status</string>
|
||||||
<string name="Notify_ChannelService_Long">Shown constantly to inform you about background checking.</string>
|
<string name="Notify_ChannelService_Long">Shown constantly to inform you about background checking.</string>
|
||||||
|
<string name="Notify_Error_StackTrace_Toast">Stacktraces copied to clipboard.</string>
|
||||||
|
<string name="Notify_Error_CopyAction">Copy stacktrace to clipboard</string>
|
||||||
|
<string name="Notify_Error_Retrying">failed, retrying…</string>
|
||||||
</resources>
|
</resources>
|
||||||
|
|
19
android/src/com/unciv/app/CopyToClipboardReceiver.kt
Normal file
19
android/src/com/unciv/app/CopyToClipboardReceiver.kt
Normal file
|
@ -0,0 +1,19 @@
|
||||||
|
package com.unciv.app
|
||||||
|
|
||||||
|
import android.content.*
|
||||||
|
import android.widget.Toast
|
||||||
|
import com.badlogic.gdx.backends.android.AndroidApplication
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This Receiver can be called from an Action on the error Notification shown by MultiplayerTurnCheckWorker.
|
||||||
|
* It copies the text given to it to clipboard and then shows a Toast.
|
||||||
|
* It's intended to help find out why the Turn Notifier failed.
|
||||||
|
*/
|
||||||
|
class CopyToClipboardReceiver : BroadcastReceiver() {
|
||||||
|
override fun onReceive(context: Context, intent: Intent) {
|
||||||
|
val clipboard: ClipboardManager = context.getSystemService(AndroidApplication.CLIPBOARD_SERVICE) as ClipboardManager
|
||||||
|
val clip = ClipData.newPlainText("exception", intent.getStringExtra(MultiplayerTurnCheckWorker.CLIPBOARD_EXTRA))
|
||||||
|
clipboard.setPrimaryClip(clip)
|
||||||
|
Toast.makeText(context, context.resources.getString(R.string.Notify_Error_StackTrace_Toast), Toast.LENGTH_SHORT).show()
|
||||||
|
}
|
||||||
|
}
|
|
@ -15,6 +15,9 @@ import com.badlogic.gdx.backends.android.AndroidApplication
|
||||||
import com.unciv.logic.GameInfo
|
import com.unciv.logic.GameInfo
|
||||||
import com.unciv.models.metadata.GameSettings
|
import com.unciv.models.metadata.GameSettings
|
||||||
import com.unciv.ui.worldscreen.mainmenu.OnlineMultiplayer
|
import com.unciv.ui.worldscreen.mainmenu.OnlineMultiplayer
|
||||||
|
import java.io.PrintWriter
|
||||||
|
import java.io.StringWriter
|
||||||
|
import java.io.Writer
|
||||||
import java.util.*
|
import java.util.*
|
||||||
import java.util.concurrent.TimeUnit
|
import java.util.concurrent.TimeUnit
|
||||||
|
|
||||||
|
@ -24,6 +27,7 @@ class MultiplayerTurnCheckWorker(appContext: Context, workerParams: WorkerParame
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
const val WORK_TAG = "UNCIV_MULTIPLAYER_TURN_CHECKER_WORKER"
|
const val WORK_TAG = "UNCIV_MULTIPLAYER_TURN_CHECKER_WORKER"
|
||||||
|
const val CLIPBOARD_EXTRA = "CLIPBOARD_STRING"
|
||||||
const val NOTIFICATION_ID_SERVICE = 1
|
const val NOTIFICATION_ID_SERVICE = 1
|
||||||
const val NOTIFICATION_ID_INFO = 2
|
const val NOTIFICATION_ID_INFO = 2
|
||||||
|
|
||||||
|
@ -202,41 +206,49 @@ class MultiplayerTurnCheckWorker(appContext: Context, workerParams: WorkerParame
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun doWork(): Result {
|
override fun doWork(): Result {
|
||||||
|
val showPersistNotific = inputData.getBoolean(PERSISTENT_NOTIFICATION_ENABLED, true)
|
||||||
|
val configuredDelay = inputData.getInt(CONFIGURED_DELAY, 5)
|
||||||
try {
|
try {
|
||||||
val latestGame = OnlineMultiplayer().tryDownloadGame(inputData.getString(GAME_ID)!!)
|
val currentTurnPlayer = OnlineMultiplayer().tryDownloadCurrentTurnCiv(inputData.getString(GAME_ID)!!)
|
||||||
if (latestGame.currentPlayerCiv.playerId == inputData.getString(USER_ID)!!) {
|
if (currentTurnPlayer.playerId == inputData.getString(USER_ID)!!) {
|
||||||
notifyUserAboutTurn(applicationContext)
|
notifyUserAboutTurn(applicationContext)
|
||||||
with(NotificationManagerCompat.from(applicationContext)) {
|
with(NotificationManagerCompat.from(applicationContext)) {
|
||||||
cancel(NOTIFICATION_ID_SERVICE)
|
cancel(NOTIFICATION_ID_SERVICE)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
updatePersistentNotification(inputData)
|
if (showPersistNotific) { updatePersistentNotification(inputData) }
|
||||||
// We have to reset the fail counter since no exception appeared
|
// We have to reset the fail counter since no exception appeared
|
||||||
val inputDataFailReset = Data.Builder().putAll(inputData).putInt(FAIL_COUNT, 0).build()
|
val inputDataFailReset = Data.Builder().putAll(inputData).putInt(FAIL_COUNT, 0).build()
|
||||||
enqueue(applicationContext, inputData.getInt(CONFIGURED_DELAY, 5), inputDataFailReset)
|
enqueue(applicationContext, configuredDelay, inputDataFailReset)
|
||||||
}
|
}
|
||||||
} catch (ex: Exception) {
|
} catch (ex: Exception) {
|
||||||
val failCount = inputData.getInt(FAIL_COUNT, 0)
|
val failCount = inputData.getInt(FAIL_COUNT, 0)
|
||||||
if (failCount > 3) {
|
if (failCount > 3) {
|
||||||
showErrorNotification()
|
showErrorNotification(getStackTraceString(ex))
|
||||||
with(NotificationManagerCompat.from(applicationContext)) {
|
with(NotificationManagerCompat.from(applicationContext)) {
|
||||||
cancel(NOTIFICATION_ID_SERVICE)
|
cancel(NOTIFICATION_ID_SERVICE)
|
||||||
}
|
}
|
||||||
return Result.failure()
|
return Result.failure()
|
||||||
} else {
|
} else {
|
||||||
|
if (showPersistNotific) { showPersistentNotification(applicationContext,
|
||||||
|
applicationContext.resources.getString(R.string.Notify_Error_Retrying), configuredDelay.toString()) }
|
||||||
// If check fails, retry in one minute.
|
// If check fails, retry in one minute.
|
||||||
// Makes sense, since checks only happen if Internet is available in principle.
|
// Makes sense, since checks only happen if Internet is available in principle.
|
||||||
// Therefore a failure means either a problem with the GameInfo or with Dropbox.
|
// Therefore a failure means either a problem with the GameInfo or with Dropbox.
|
||||||
val inputDataFailIncrease = Data.Builder().putAll(inputData).putInt(FAIL_COUNT, failCount + 1).build()
|
val inputDataFailIncrease = Data.Builder().putAll(inputData).putInt(FAIL_COUNT, failCount + 1).build()
|
||||||
enqueue(applicationContext, 1, inputDataFailIncrease)
|
enqueue(applicationContext, 1, inputDataFailIncrease)
|
||||||
// Persistent Notification is not updated, because user may think check succeed.
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return Result.success()
|
return Result.success()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun getStackTraceString(ex: Exception): String {
|
||||||
|
val writer: Writer = StringWriter()
|
||||||
|
ex.printStackTrace(PrintWriter(writer))
|
||||||
|
return writer.toString()
|
||||||
|
}
|
||||||
|
|
||||||
private fun updatePersistentNotification(inputData: Data) {
|
private fun updatePersistentNotification(inputData: Data) {
|
||||||
if (inputData.getBoolean(PERSISTENT_NOTIFICATION_ENABLED, true)) {
|
|
||||||
val cal = GregorianCalendar.getInstance()
|
val cal = GregorianCalendar.getInstance()
|
||||||
val hour = cal.get(GregorianCalendar.HOUR_OF_DAY).toString()
|
val hour = cal.get(GregorianCalendar.HOUR_OF_DAY).toString()
|
||||||
var minute = cal.get(GregorianCalendar.MINUTE).toString()
|
var minute = cal.get(GregorianCalendar.MINUTE).toString()
|
||||||
|
@ -248,14 +260,18 @@ class MultiplayerTurnCheckWorker(appContext: Context, workerParams: WorkerParame
|
||||||
showPersistentNotification(applicationContext, displayTime,
|
showPersistentNotification(applicationContext, displayTime,
|
||||||
inputData.getInt(CONFIGURED_DELAY, 5).toString())
|
inputData.getInt(CONFIGURED_DELAY, 5).toString())
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
private fun showErrorNotification() {
|
private fun showErrorNotification(stackTraceString: String) {
|
||||||
val pendingIntent: PendingIntent =
|
val pendingLaunchGameIntent: PendingIntent =
|
||||||
Intent(applicationContext, AndroidLauncher::class.java).let { notificationIntent ->
|
Intent(applicationContext, AndroidLauncher::class.java).let { notificationIntent ->
|
||||||
PendingIntent.getActivity(applicationContext, 0, notificationIntent, 0)
|
PendingIntent.getActivity(applicationContext, 0, notificationIntent, 0)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
val pendingCopyClipboardIntent: PendingIntent =
|
||||||
|
Intent(applicationContext, CopyToClipboardReceiver::class.java).putExtra(CLIPBOARD_EXTRA, stackTraceString)
|
||||||
|
.let { notificationIntent -> PendingIntent.getBroadcast(applicationContext,0, notificationIntent, 0)
|
||||||
|
}
|
||||||
|
|
||||||
val notification: NotificationCompat.Builder = NotificationCompat.Builder(applicationContext, NOTIFICATION_CHANNEL_ID_INFO)
|
val notification: NotificationCompat.Builder = NotificationCompat.Builder(applicationContext, NOTIFICATION_CHANNEL_ID_INFO)
|
||||||
.setPriority(NotificationManagerCompat.IMPORTANCE_DEFAULT) // No direct user action expected
|
.setPriority(NotificationManagerCompat.IMPORTANCE_DEFAULT) // No direct user action expected
|
||||||
.setContentTitle(applicationContext.resources.getString(R.string.Notify_Error_Short))
|
.setContentTitle(applicationContext.resources.getString(R.string.Notify_Error_Short))
|
||||||
|
@ -264,9 +280,10 @@ class MultiplayerTurnCheckWorker(appContext: Context, workerParams: WorkerParame
|
||||||
// without at least vibrate, some Android versions don't show a heads-up notification
|
// without at least vibrate, some Android versions don't show a heads-up notification
|
||||||
.setDefaults(DEFAULT_VIBRATE)
|
.setDefaults(DEFAULT_VIBRATE)
|
||||||
.setLights(Color.YELLOW, 300, 100)
|
.setLights(Color.YELLOW, 300, 100)
|
||||||
.setContentIntent(pendingIntent)
|
.setContentIntent(pendingLaunchGameIntent)
|
||||||
.setCategory(NotificationCompat.CATEGORY_ERROR)
|
.setCategory(NotificationCompat.CATEGORY_ERROR)
|
||||||
.setOngoing(false)
|
.setOngoing(false)
|
||||||
|
.addAction(0, applicationContext.resources.getString(R.string.Notify_Error_CopyAction), pendingCopyClipboardIntent)
|
||||||
|
|
||||||
with(NotificationManagerCompat.from(applicationContext)) {
|
with(NotificationManagerCompat.from(applicationContext)) {
|
||||||
notify(NOTIFICATION_ID_INFO, notification.build())
|
notify(NOTIFICATION_ID_INFO, notification.build())
|
||||||
|
|
|
@ -3,6 +3,7 @@ package com.unciv.logic
|
||||||
import com.badlogic.gdx.Gdx
|
import com.badlogic.gdx.Gdx
|
||||||
import com.badlogic.gdx.files.FileHandle
|
import com.badlogic.gdx.files.FileHandle
|
||||||
import com.badlogic.gdx.utils.Json
|
import com.badlogic.gdx.utils.Json
|
||||||
|
import com.unciv.logic.civilization.CivilizationInfo
|
||||||
import com.unciv.models.metadata.GameSettings
|
import com.unciv.models.metadata.GameSettings
|
||||||
import com.unciv.ui.utils.ImageGetter
|
import com.unciv.ui.utils.ImageGetter
|
||||||
import java.io.File
|
import java.io.File
|
||||||
|
@ -95,5 +96,15 @@ class GameSaver {
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns current turn's player from GameInfo JSON-String for multiplayer.
|
||||||
|
* Does not initialize transitive GameInfo data.
|
||||||
|
* It is therefore stateless and save to call for Multiplayer Turn Notifier, unlike gameInfoFromString().
|
||||||
|
*/
|
||||||
|
fun currentTurnCivFromString(gameData: String): CivilizationInfo {
|
||||||
|
val game = json().fromJson(GameInfo::class.java, gameData)
|
||||||
|
return game.getCivilization(game.currentPlayer)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -2,6 +2,7 @@ package com.unciv.ui.worldscreen.mainmenu
|
||||||
|
|
||||||
import com.unciv.logic.GameInfo
|
import com.unciv.logic.GameInfo
|
||||||
import com.unciv.logic.GameSaver
|
import com.unciv.logic.GameSaver
|
||||||
|
import com.unciv.logic.civilization.CivilizationInfo
|
||||||
import com.unciv.ui.saves.Gzip
|
import com.unciv.ui.saves.Gzip
|
||||||
import java.io.BufferedReader
|
import java.io.BufferedReader
|
||||||
import java.io.DataOutputStream
|
import java.io.DataOutputStream
|
||||||
|
@ -9,7 +10,7 @@ import java.io.InputStream
|
||||||
import java.io.InputStreamReader
|
import java.io.InputStreamReader
|
||||||
import java.net.HttpURLConnection
|
import java.net.HttpURLConnection
|
||||||
import java.net.URL
|
import java.net.URL
|
||||||
import java.nio.charset.StandardCharsets
|
import java.nio.charset.Charset
|
||||||
|
|
||||||
class DropBox {
|
class DropBox {
|
||||||
|
|
||||||
|
@ -27,7 +28,8 @@ class DropBox {
|
||||||
|
|
||||||
try {
|
try {
|
||||||
if (data != "") {
|
if (data != "") {
|
||||||
val postData: ByteArray = data.toByteArray(StandardCharsets.UTF_8)
|
// StandardCharsets.UTF_8 requires API 19
|
||||||
|
val postData: ByteArray = data.toByteArray(Charset.forName("UTF-8"))
|
||||||
val outputStream = DataOutputStream(outputStream)
|
val outputStream = DataOutputStream(outputStream)
|
||||||
outputStream.write(postData)
|
outputStream.write(postData)
|
||||||
outputStream.flush()
|
outputStream.flush()
|
||||||
|
@ -103,4 +105,14 @@ class OnlineMultiplayer {
|
||||||
val zippedGameInfo = DropBox().downloadFileAsString(getGameLocation(gameId))
|
val zippedGameInfo = DropBox().downloadFileAsString(getGameLocation(gameId))
|
||||||
return GameSaver().gameInfoFromString(Gzip.unzip(zippedGameInfo))
|
return GameSaver().gameInfoFromString(Gzip.unzip(zippedGameInfo))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns current turn's player.
|
||||||
|
* Does not initialize transitive GameInfo data.
|
||||||
|
* It is therefore stateless and save to call for Multiplayer Turn Notifier, unlike tryDownloadGame().
|
||||||
|
*/
|
||||||
|
fun tryDownloadCurrentTurnCiv(gameId: String): CivilizationInfo {
|
||||||
|
val zippedGameInfo = DropBox().downloadFileAsString(getGameLocation(gameId))
|
||||||
|
return GameSaver().currentTurnCivFromString(Gzip.unzip(zippedGameInfo))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue