Multiplayer Notification Fix for random Termination (#2024)
* Reworked Notification service to remove Nullpointer crashes due to non-final static variables being null. Instead moved all state to Worker specific architecture. Also switched to Android based localization because core-based one can be unavailable to worker. * Added missing blanks * Added more internationalization Added missing androidX declaration (neccessary for newer Gradle versions) Updated Gradle for new Android Studio Version * Optimization * Removed missing translations error
This commit is contained in:
parent
3ccd457759
commit
9188c7790f
11 changed files with 116 additions and 89 deletions
1
.gitignore
vendored
1
.gitignore
vendored
|
@ -123,7 +123,6 @@ Thumbs.db
|
|||
!/ios-moe/xcode/*.xcodeproj/xcshareddata
|
||||
!/ios-moe/xcode/*.xcodeproj/project.pbxproj
|
||||
/ios-moe/xcode/native/
|
||||
gradle.properties
|
||||
SaveFiles/
|
||||
android/android-release.apk
|
||||
android/assets/GameSettings.json
|
||||
|
|
|
@ -1747,15 +1747,6 @@ Haka War Dance = Haka-Kriegstanz
|
|||
Rejuvenation = Verjüngung
|
||||
All healing effects doubled = Alle Heilungseffekte verdoppelt
|
||||
# Multiplayer Turn Checker Service
|
||||
An error has occured = Ein Fehler ist aufgetreten
|
||||
Multiplayer turn notifier service terminated = Multiplayer Zug Benachrichtigungsdienst wurde beendet
|
||||
Your friends are waiting on your turn. = Deine Freunde warten auf deinen Zug.
|
||||
Unciv - It's your turn! = Unciv - Du bist am Zug!
|
||||
Unciv will inform you when it's your turn in multiplayer. = Unciv wird dich benachrichtigen, wenn du im Multiplayer am Zug bist.
|
||||
Last online turn check: [lastTimeChecked] = Letzter online Zug Check: [lastTimeChecked]
|
||||
Checks ca. every [checkPeriod] minute(s) when Internet available. = Prüft etwa alle [checkPeriod] Minute(n) wenn Internet vorhanden.
|
||||
Configurable in Unciv options menu. = Konfigurierbar im Unciv Optionsmenü.
|
||||
Unciv multiplayer turn notifier running = Unciv Multiplayer Zug Benachrichtiger läuft.
|
||||
Multiplayer options = Multiplayer Einstellungen
|
||||
Enable out-of-game turn notifications = Aktiviere Zug Benachrichtigungen außerhalb des Spiels
|
||||
Time between turn checks out-of-game (in minutes) = Intervall zwischen Zug Prüfungen (in Minuten)
|
||||
|
|
|
@ -1743,15 +1743,6 @@ Haka War Dance =
|
|||
Rejuvenation =
|
||||
All healing effects doubled =
|
||||
# Multiplayer Turn Checker Service
|
||||
An error has occured =
|
||||
Multiplayer turn notifier service terminated =
|
||||
Your friends are waiting on your turn. =
|
||||
Unciv - It's your turn! =
|
||||
Unciv will inform you when it's your turn in multiplayer. =
|
||||
Last online turn check: [lastTimeChecked] =
|
||||
Checks ca. every [checkPeriod] minute(s) when Internet available. =
|
||||
Configurable in Unciv options menu. =
|
||||
Unciv multiplayer turn notifier running =
|
||||
Multiplayer options =
|
||||
Enable out-of-game turn notifications =
|
||||
Time between turn checks out-of-game (in minutes) =
|
||||
|
|
|
@ -62,6 +62,9 @@ android {
|
|||
}
|
||||
}
|
||||
}
|
||||
lintOptions {
|
||||
disable 'MissingTranslation'
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
@ -123,7 +126,7 @@ task run(type: Exec) {
|
|||
|
||||
dependencies {
|
||||
implementation 'androidx.core:core:1.2.0'
|
||||
implementation "androidx.work:work-runtime-ktx:2.3.1"
|
||||
implementation "androidx.work:work-runtime-ktx:2.3.2"
|
||||
}
|
||||
|
||||
// sets up the Android Eclipse project, using the old Ant based build.
|
||||
|
|
2
android/gradle.properties
Normal file
2
android/gradle.properties
Normal file
|
@ -0,0 +1,2 @@
|
|||
android.useAndroidX=true
|
||||
android.enableJetifier=true
|
17
android/res/values-de/strings.xml
Normal file
17
android/res/values-de/strings.xml
Normal file
|
@ -0,0 +1,17 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<string name="app_name">UnCiv</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_Error_Short">Ein Fehler ist aufgetreten</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_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_P3">Minute(n) wenn Internet vorhanden.</string>
|
||||
<string name="Notify_Persist_Long_P4">Konfigurierbar im Unciv Optionsmenü.</string>
|
||||
<string name="Notify_ChannelInfo_Short">Unciv Multiplayer Zugprüfer Ereignis</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_Long">Permanent angezeigte Benachrichtigung, welche dich über die Hintergrundaktivität des Dienstes informiert.</string>
|
||||
</resources>
|
9
android/res/values-fr-rFR/strings.xml
Normal file
9
android/res/values-fr-rFR/strings.xml
Normal file
|
@ -0,0 +1,9 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<string name="app_name">UnCiv</string>
|
||||
<string name="Notify_YourTurn_Short">Unciv - C\'est à vous !</string>
|
||||
<string name="Notify_YourTurn_Long">Vos amis attendent votre action.</string>
|
||||
<string name="Notify_Error_Long">Service de notification du tour multijoueur terminé</string>
|
||||
<string name="Notify_Error_Short">Une erreur est survenue</string>
|
||||
<string name="Notify_Persist_Long_P4">Configurable dans le menu des options de Unciv</string>
|
||||
</resources>
|
|
@ -1,4 +1,17 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<string name="app_name">UnCiv</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_Error_Short">An error has occurred</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_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_P3">minute(s) when Internet available.</string>
|
||||
<string name="Notify_Persist_Long_P4">Configurable in Unciv options menu.</string>
|
||||
<string name="Notify_ChannelInfo_Short">Unciv Multiplayer Turn Checker Alert</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_Long">Shown constantly to inform you about background checking.</string>
|
||||
</resources>
|
||||
|
|
|
@ -10,12 +10,10 @@ import android.os.Build
|
|||
import androidx.core.app.NotificationCompat
|
||||
import androidx.core.app.NotificationCompat.DEFAULT_VIBRATE
|
||||
import androidx.core.app.NotificationManagerCompat
|
||||
import androidx.core.content.ContextCompat.getSystemService
|
||||
import androidx.work.*
|
||||
import com.badlogic.gdx.backends.android.AndroidApplication
|
||||
import com.unciv.logic.GameInfo
|
||||
import com.unciv.models.metadata.GameSettings
|
||||
import com.unciv.models.translations.tr
|
||||
import com.unciv.ui.worldscreen.mainmenu.OnlineMultiplayer
|
||||
import java.util.*
|
||||
import java.util.concurrent.TimeUnit
|
||||
|
@ -37,16 +35,15 @@ class MultiplayerTurnCheckWorker(appContext: Context, workerParams: WorkerParame
|
|||
private const val NOTIFICATION_CHANNEL_ID_INFO = "UNCIV_NOTIFICATION_CHANNEL_INFO"
|
||||
private const val NOTIFICATION_CHANNEL_ID_SERVICE = "UNCIV_NOTIFICATION_CHANNEL_SERVICE_02"
|
||||
|
||||
private const val FAIL_COUNT = "FAIL_COUNT"
|
||||
private const val GAME_ID = "GAME_ID"
|
||||
private const val USER_ID = "USER_ID"
|
||||
private const val CONFIGURED_DELAY = "CONFIGURED_DELAY"
|
||||
private const val PERSISTENT_NOTIFICATION_ENABLED = "PERSISTENT_NOTIFICATION_ENABLED"
|
||||
|
||||
// These fields need to be volatile because they are set by main thread but later accessed by worker thread.
|
||||
// Classes used here need to be primitive or internally synchronized to avoid visibility issues.
|
||||
@Volatile private var failCount = 0
|
||||
@Volatile private var gameId = ""
|
||||
@Volatile private var userId = ""
|
||||
@Volatile private var configuredDelay = 5
|
||||
@Volatile private var persistentNotificationEnabled = true
|
||||
fun enqueue(appContext: Context,
|
||||
delayInMinutes: Int, inputData: Data) {
|
||||
|
||||
fun enqueue(appContext: Context, delayInMinutes: Int) {
|
||||
val constraints = Constraints.Builder()
|
||||
// If no internet is available, worker waits before becoming active.
|
||||
.setRequiredNetworkType(NetworkType.CONNECTED)
|
||||
|
@ -56,6 +53,7 @@ class MultiplayerTurnCheckWorker(appContext: Context, workerParams: WorkerParame
|
|||
.setConstraints(constraints)
|
||||
.setInitialDelay(delayInMinutes.toLong(), TimeUnit.MINUTES)
|
||||
.addTag(WORK_TAG)
|
||||
.setInputData(inputData)
|
||||
.build()
|
||||
|
||||
WorkManager.getInstance(appContext).enqueue(checkTurnWork)
|
||||
|
@ -70,8 +68,8 @@ class MultiplayerTurnCheckWorker(appContext: Context, workerParams: WorkerParame
|
|||
*/
|
||||
fun createNotificationChannelInfo(appContext: Context) {
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
||||
val name = "Unciv Multiplayer Turn Checker Alert"
|
||||
val descriptionText = "Informs you when it's your turn in multiplayer."
|
||||
val name = appContext.resources.getString(R.string.Notify_ChannelInfo_Short)
|
||||
val descriptionText = appContext.resources.getString(R.string.Notify_ChannelInfo_Long)
|
||||
val importance = NotificationManager.IMPORTANCE_HIGH
|
||||
val mChannel = NotificationChannel(NOTIFICATION_CHANNEL_ID_INFO, name, importance)
|
||||
mChannel.description = descriptionText
|
||||
|
@ -92,8 +90,8 @@ class MultiplayerTurnCheckWorker(appContext: Context, workerParams: WorkerParame
|
|||
*/
|
||||
fun createNotificationChannelService(appContext: Context) {
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
||||
val name = "Unciv Multiplayer Turn Checker Persistent Status"
|
||||
val descriptionText = "Shown constantly to inform you about background checking."
|
||||
val name = appContext.resources.getString(R.string.Notify_ChannelService_Short)
|
||||
val descriptionText = appContext.resources.getString(R.string.Notify_ChannelService_Long)
|
||||
val importance = NotificationManager.IMPORTANCE_MIN
|
||||
val mChannel = NotificationChannel(NOTIFICATION_CHANNEL_ID_SERVICE, name, importance)
|
||||
mChannel.setShowBadge(false)
|
||||
|
@ -110,7 +108,6 @@ class MultiplayerTurnCheckWorker(appContext: Context, workerParams: WorkerParame
|
|||
* It is not technically necessary for the Worker, since it is not a Service.
|
||||
*/
|
||||
fun showPersistentNotification(appContext: Context, lastTimeChecked: String, checkPeriod: String) {
|
||||
if (persistentNotificationEnabled) {
|
||||
val pendingIntent: PendingIntent =
|
||||
Intent(appContext, AndroidLauncher::class.java).let { notificationIntent ->
|
||||
PendingIntent.getActivity(appContext, 0, notificationIntent, 0)
|
||||
|
@ -118,11 +115,12 @@ class MultiplayerTurnCheckWorker(appContext: Context, workerParams: WorkerParame
|
|||
|
||||
val notification: NotificationCompat.Builder = NotificationCompat.Builder(appContext, NOTIFICATION_CHANNEL_ID_SERVICE)
|
||||
.setPriority(NotificationManagerCompat.IMPORTANCE_MIN) // it's only a status
|
||||
.setContentTitle(("Last online turn check: [$lastTimeChecked]").tr())
|
||||
.setContentTitle(appContext.resources.getString(R.string.Notify_Persist_Short) + " " + lastTimeChecked)
|
||||
.setStyle(NotificationCompat.BigTextStyle()
|
||||
.bigText("Unciv will inform you when it's your turn in multiplayer.".tr() + " " +
|
||||
"Checks ca. every [$checkPeriod] minute(s) when Internet available.".tr()
|
||||
+ " " + "Configurable in Unciv options menu.".tr()))
|
||||
.bigText(appContext.resources.getString(R.string.Notify_Persist_Long_P1) + " " +
|
||||
appContext.resources.getString(R.string.Notify_Persist_Long_P2) + " " + checkPeriod + " "
|
||||
+ appContext.resources.getString(R.string.Notify_Persist_Long_P3)
|
||||
+ " " + appContext.resources.getString(R.string.Notify_Persist_Long_P4)))
|
||||
.setSmallIcon(R.drawable.uncivicon2)
|
||||
.setContentIntent(pendingIntent)
|
||||
.setCategory(NotificationCompat.CATEGORY_SERVICE)
|
||||
|
@ -134,7 +132,6 @@ class MultiplayerTurnCheckWorker(appContext: Context, workerParams: WorkerParame
|
|||
notify(NOTIFICATION_ID_INFO, notification.build())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun notifyUserAboutTurn(applicationContext: Context) {
|
||||
val pendingIntent: PendingIntent =
|
||||
|
@ -142,11 +139,11 @@ class MultiplayerTurnCheckWorker(appContext: Context, workerParams: WorkerParame
|
|||
PendingIntent.getActivity(applicationContext, 0, notificationIntent, 0)
|
||||
}
|
||||
|
||||
val contentTitle = "Unciv - It's your turn!".tr()
|
||||
val contentTitle = applicationContext.resources.getString(R.string.Notify_YourTurn_Short)
|
||||
val notification: NotificationCompat.Builder = NotificationCompat.Builder(applicationContext, NOTIFICATION_CHANNEL_ID_INFO)
|
||||
.setPriority(NotificationManagerCompat.IMPORTANCE_HIGH) // people are waiting!
|
||||
.setContentTitle(contentTitle)
|
||||
.setContentText("Your friends are waiting on your turn.".tr())
|
||||
.setContentText(applicationContext.resources.getString(R.string.Notify_YourTurn_Long))
|
||||
.setTicker(contentTitle)
|
||||
// without at least vibrate, some Android versions don't show a heads-up notification
|
||||
.setDefaults(DEFAULT_VIBRATE)
|
||||
|
@ -166,15 +163,16 @@ class MultiplayerTurnCheckWorker(appContext: Context, workerParams: WorkerParame
|
|||
// May be useful to remind a player that he forgot to complete his turn.
|
||||
notifyUserAboutTurn(applicationContext)
|
||||
} else {
|
||||
gameId = gameInfo.gameId
|
||||
userId = settings.userId
|
||||
configuredDelay = settings.multiplayerTurnCheckerDelayInMinutes
|
||||
persistentNotificationEnabled = settings.multiplayerTurnCheckerPersistentNotificationEnabled
|
||||
val inputData = workDataOf(Pair(FAIL_COUNT, 0), Pair(GAME_ID, gameInfo.gameId),
|
||||
Pair(USER_ID, settings.userId), Pair(CONFIGURED_DELAY, settings.multiplayerTurnCheckerDelayInMinutes),
|
||||
Pair(PERSISTENT_NOTIFICATION_ENABLED, settings.multiplayerTurnCheckerPersistentNotificationEnabled))
|
||||
|
||||
if (settings.multiplayerTurnCheckerPersistentNotificationEnabled) {
|
||||
showPersistentNotification(applicationContext,
|
||||
"—", settings.multiplayerTurnCheckerDelayInMinutes.toString())
|
||||
}
|
||||
// Initial check always happens after a minute, ignoring delay config. Better user experience this way.
|
||||
enqueue(applicationContext, 1)
|
||||
enqueue(applicationContext, 1, inputData)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -205,37 +203,40 @@ class MultiplayerTurnCheckWorker(appContext: Context, workerParams: WorkerParame
|
|||
|
||||
override fun doWork(): Result {
|
||||
try {
|
||||
val latestGame = OnlineMultiplayer().tryDownloadGame(gameId)
|
||||
if (latestGame.currentPlayerCiv.playerId == userId) {
|
||||
val latestGame = OnlineMultiplayer().tryDownloadGame(inputData.getString(GAME_ID)!!)
|
||||
if (latestGame.currentPlayerCiv.playerId == inputData.getString(USER_ID)!!) {
|
||||
notifyUserAboutTurn(applicationContext)
|
||||
with(NotificationManagerCompat.from(applicationContext)) {
|
||||
cancel(NOTIFICATION_ID_SERVICE)
|
||||
}
|
||||
} else {
|
||||
enqueue(applicationContext, configuredDelay)
|
||||
updatePersistentNotification()
|
||||
updatePersistentNotification(inputData)
|
||||
// We have to reset the fail counter since no exception appeared
|
||||
val inputDataFailReset = Data.Builder().putAll(inputData).putInt(FAIL_COUNT, 0).build()
|
||||
enqueue(applicationContext, inputData.getInt(CONFIGURED_DELAY, 5), inputDataFailReset)
|
||||
}
|
||||
failCount = 0
|
||||
} catch (ex: Exception) {
|
||||
if (failCount++ > 3) {
|
||||
val failCount = inputData.getInt(FAIL_COUNT, 0)
|
||||
if (failCount > 3) {
|
||||
showErrorNotification()
|
||||
with(NotificationManagerCompat.from(applicationContext)) {
|
||||
cancel(NOTIFICATION_ID_SERVICE)
|
||||
}
|
||||
failCount = 0 // Otherwise the notification service would be forever stuck in error mode.
|
||||
return Result.failure()
|
||||
} else {
|
||||
// If check fails, retry in one minute.
|
||||
// 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.
|
||||
enqueue(applicationContext, 1)
|
||||
updatePersistentNotification()
|
||||
val inputDataFailIncrease = Data.Builder().putAll(inputData).putInt(FAIL_COUNT, failCount + 1).build()
|
||||
enqueue(applicationContext, 1, inputDataFailIncrease)
|
||||
// Persistent Notification is not updated, because user may think check succeed.
|
||||
}
|
||||
}
|
||||
return Result.success()
|
||||
}
|
||||
|
||||
private fun updatePersistentNotification() {
|
||||
private fun updatePersistentNotification(inputData: Data) {
|
||||
if (inputData.getBoolean(PERSISTENT_NOTIFICATION_ENABLED, true)) {
|
||||
val cal = GregorianCalendar.getInstance()
|
||||
val hour = cal.get(GregorianCalendar.HOUR_OF_DAY).toString()
|
||||
var minute = cal.get(GregorianCalendar.MINUTE).toString()
|
||||
|
@ -245,7 +246,8 @@ class MultiplayerTurnCheckWorker(appContext: Context, workerParams: WorkerParame
|
|||
val displayTime = "$hour:$minute"
|
||||
|
||||
showPersistentNotification(applicationContext, displayTime,
|
||||
configuredDelay.toString())
|
||||
inputData.getInt(CONFIGURED_DELAY, 5).toString())
|
||||
}
|
||||
}
|
||||
|
||||
private fun showErrorNotification() {
|
||||
|
@ -256,8 +258,8 @@ class MultiplayerTurnCheckWorker(appContext: Context, workerParams: WorkerParame
|
|||
|
||||
val notification: NotificationCompat.Builder = NotificationCompat.Builder(applicationContext, NOTIFICATION_CHANNEL_ID_INFO)
|
||||
.setPriority(NotificationManagerCompat.IMPORTANCE_DEFAULT) // No direct user action expected
|
||||
.setContentTitle("An error has occured".tr())
|
||||
.setContentText("Multiplayer turn notifier service terminated".tr())
|
||||
.setContentTitle(applicationContext.resources.getString(R.string.Notify_Error_Short))
|
||||
.setContentText(applicationContext.resources.getString(R.string.Notify_Error_Long))
|
||||
.setSmallIcon(R.drawable.uncivicon2)
|
||||
// without at least vibrate, some Android versions don't show a heads-up notification
|
||||
.setDefaults(DEFAULT_VIBRATE)
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
buildscript {
|
||||
|
||||
ext.kotlinVersion = '1.3.50'
|
||||
ext.kotlinVersion = '1.3.61'
|
||||
|
||||
repositories {
|
||||
// Chinese mirrors for quicker loading for chinese devs - uncomment if you're chinese
|
||||
|
@ -18,7 +18,7 @@ buildscript {
|
|||
dependencies {
|
||||
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlinVersion"
|
||||
classpath 'de.richsource.gradle.plugins:gwt-gradle-plugin:0.6'
|
||||
classpath 'com.android.tools.build:gradle:3.5.3'
|
||||
classpath 'com.android.tools.build:gradle:3.6.0'
|
||||
classpath 'com.mobidevelop.robovm:robovm-gradle-plugin:2.3.1'
|
||||
|
||||
// This is for wrapping the .jar file into a standalone executable
|
||||
|
|
4
gradle/wrapper/gradle-wrapper.properties
vendored
4
gradle/wrapper/gradle-wrapper.properties
vendored
|
@ -1,6 +1,6 @@
|
|||
#Sat Aug 24 11:02:13 CST 2019
|
||||
#Thu Feb 27 10:30:48 CET 2020
|
||||
distributionBase=GRADLE_USER_HOME
|
||||
distributionPath=wrapper/dists
|
||||
zipStoreBase=GRADLE_USER_HOME
|
||||
zipStorePath=wrapper/dists
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-5.4.1-all.zip
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-5.6.4-all.zip
|
||||
|
|
Loading…
Reference in a new issue