Use a global companion

This commit is contained in:
sim 2023-08-31 01:46:06 +02:00
parent ded36d2e6e
commit 2ba1d35c66
8 changed files with 108 additions and 109 deletions

View File

@ -0,0 +1,15 @@
package org.unifiedpush.distributor.nextpush
import java.util.Calendar
import java.util.concurrent.atomic.AtomicBoolean
import java.util.concurrent.atomic.AtomicInteger
object AppCompanion {
val booting = AtomicBoolean(false)
val hasInternet = AtomicBoolean(false)
val started = AtomicBoolean(false)
val pinged = AtomicBoolean(false)
val bufferedResponseChecked = AtomicBoolean(false)
val keepalive = AtomicInteger(900)
var lastEventDate: Calendar? = null
}

View File

@ -7,15 +7,13 @@ import com.google.gson.Gson
import okhttp3.Response
import okhttp3.sse.EventSource
import okhttp3.sse.EventSourceListener
import org.unifiedpush.distributor.nextpush.AppCompanion
import org.unifiedpush.distributor.nextpush.api.response.SSEResponse
import org.unifiedpush.distributor.nextpush.distributor.Distributor.deleteAppFromSSE
import org.unifiedpush.distributor.nextpush.distributor.Distributor.sendMessage
import org.unifiedpush.distributor.nextpush.receivers.StartReceiver
import org.unifiedpush.distributor.nextpush.services.FailureHandler
import org.unifiedpush.distributor.nextpush.services.RestartNetworkCallback
import org.unifiedpush.distributor.nextpush.services.RestartWorker
import org.unifiedpush.distributor.nextpush.services.StartService
import org.unifiedpush.distributor.nextpush.services.StartService.StartServiceCompanion.bufferedResponseChecked
import org.unifiedpush.distributor.nextpush.utils.LowKeepAliveNotification
import org.unifiedpush.distributor.nextpush.utils.NoPingNotification
import org.unifiedpush.distributor.nextpush.utils.NoStartNotification
@ -31,10 +29,8 @@ class SSEListener(val context: Context) : EventSourceListener() {
override fun onOpen(eventSource: EventSource, response: Response) {
FailureHandler.newEventSource(context, eventSource)
startingTimer?.cancel()
if (!bufferedResponseChecked) {
if (StartReceiver.booting) {
StartReceiver.booting = false
} else {
if (!AppCompanion.bufferedResponseChecked.get()) {
if (!AppCompanion.booting.getAndSet(false)) {
startingTimer = Timer().schedule(45_000L /* 45secs */) {
StartService.stopService()
NoStartNotification(context).show()
@ -56,27 +52,29 @@ class SSEListener(val context: Context) : EventSourceListener() {
override fun onEvent(eventSource: EventSource, id: String?, type: String?, data: String) {
Log.d(TAG, "New SSE message event: $type")
StartService.wakeLock?.acquire(30000L /*30 secs*/)
lastEventDate = Calendar.getInstance()
AppCompanion.lastEventDate = Calendar.getInstance()
when (type) {
"start" -> {
started = true
AppCompanion.started.set(true)
startingTimer?.cancel()
bufferedResponseChecked = true
AppCompanion.bufferedResponseChecked.set(true)
NoStartNotification(context).delete()
}
"ping" -> {
pinged = true
FailureHandler.newPing()
AppCompanion.pinged.set(true)
FailureHandler.newPing(context)
}
"keepalive" -> {
val message = Gson().fromJson(data, SSEResponse::class.java)
keepalive = message.keepalive
Log.d(TAG, "New keepalive: $keepalive")
if (keepalive < 25) {
LowKeepAliveNotification(context, it).show()
} else {
LowKeepAliveNotification(context, it).delete()
message.keepalive.let {
AppCompanion.keepalive.set(it)
Log.d(TAG, "New keepalive: $it")
if (it < 25) {
LowKeepAliveNotification(context, it).show()
} else {
LowKeepAliveNotification(context, it).delete()
}
}
}
"message" -> {
@ -118,7 +116,7 @@ class SSEListener(val context: Context) : EventSourceListener() {
response?.let {
Log.d(TAG, "onFailure: ${it.code}")
}
if (!RestartNetworkCallback.hasInternet) {
if (!AppCompanion.hasInternet.get()) {
Log.d(TAG, "No Internet: do not restart")
FailureHandler.once(eventSource)
clearVars()
@ -126,7 +124,7 @@ class SSEListener(val context: Context) : EventSourceListener() {
}
FailureHandler.newFail(context, eventSource)
clearVars()
val delay = when (FailureHandler.nFails) {
val delay = when (FailureHandler.nFails()) {
1 -> 2 // 2sec
2 -> 5 // 5sec
3 -> 20 // 20sec
@ -150,18 +148,11 @@ class SSEListener(val context: Context) : EventSourceListener() {
private fun clearVars() {
startingTimer?.cancel()
started = false
pinged = false
AppCompanion.started.set(false)
AppCompanion.pinged.set(false)
}
companion object {
var lastEventDate: Calendar? = null
var keepalive = 900
private set
private var startingTimer: TimerTask? = null
var pinged = false
private set
var started = false
private set
}
}

View File

@ -3,18 +3,15 @@ package org.unifiedpush.distributor.nextpush.receivers
import android.content.BroadcastReceiver
import android.content.Context
import android.content.Intent
import org.unifiedpush.distributor.nextpush.AppCompanion
import org.unifiedpush.distributor.nextpush.services.RestartWorker
class StartReceiver : BroadcastReceiver() {
override fun onReceive(context: Context, intent: Intent) {
booting = true
AppCompanion.booting.set(true)
if (intent.action == Intent.ACTION_BOOT_COMPLETED) {
RestartWorker.startPeriodic(context)
}
}
companion object {
var booting = false
}
}

View File

@ -3,61 +3,59 @@ package org.unifiedpush.distributor.nextpush.services
import android.content.Context
import android.util.Log
import okhttp3.sse.EventSource
import org.unifiedpush.distributor.nextpush.api.SSEListener
import org.unifiedpush.distributor.nextpush.AppCompanion
import org.unifiedpush.distributor.nextpush.utils.DisconnectedNotification
import org.unifiedpush.distributor.nextpush.utils.NoPingNotification
import org.unifiedpush.distributor.nextpush.utils.TAG
import java.util.concurrent.atomic.AtomicInteger
import java.util.concurrent.atomic.AtomicReference
object FailureHandler {
private var ttlFails = 0
var nFails = 0
private set(value) {
if (value > 0) {
ttlFails += 1
}
field = value
}
private var nFailsBeforePing = 0
private val ttlFails = AtomicInteger(0)
private val nFails = AtomicInteger(0)
private val nFailsBeforePing = AtomicInteger(0)
// This is the last eventSource opened
private var eventSource: EventSource? = null
set(value) {
field?.cancel()
field = value
}
private val eventSource: AtomicReference<EventSource?> = AtomicReference(null)
private fun isRightEventSource(eventSource: EventSource?): Boolean {
return this.eventSource.get()?.let {
it == eventSource
} ?: true
}
fun newEventSource(context: Context, eventSource: EventSource) {
Log.d(TAG, "newEvent/Eventsource: $eventSource")
this.eventSource = eventSource
nFails = 0
this.eventSource.getAndSet(eventSource)?.cancel()
nFails.set(0)
DisconnectedNotification(context).delete()
}
fun newPing() {
nFailsBeforePing = 0
fun newPing(context: Context) {
nFailsBeforePing.set(0)
NoPingNotification(context).delete()
}
fun nFails(): Int {
return nFails.get()
}
fun newFail(context: Context, eventSource: EventSource?) {
Log.d(TAG, "newFail/Eventsource: $eventSource")
// ignore fails from a possible old eventSource
// if we are already reconnected
if (this.eventSource == null || this.eventSource == eventSource) {
if (isRightEventSource(eventSource)) {
Log.d(TAG, "EventSource is known or null")
nFails++
if (nFails == 2) {
ttlFails.getAndIncrement()
if (nFails.incrementAndGet() == 2) {
DisconnectedNotification(context).show()
}
if (SSEListener.started && !SSEListener.pinged) {
if (AppCompanion.started.get() && !AppCompanion.pinged.get()) {
Log.d(TAG, "The service has started and it has never received a ping.")
nFailsBeforePing++
if (nFailsBeforePing == 5) {
if (nFailsBeforePing.incrementAndGet() == 5) {
NoPingNotification(context).show()
}
}
this.eventSource = null
this.eventSource.getAndSet(null)?.cancel()
} else {
eventSource?.cancel()
}
@ -67,10 +65,10 @@ object FailureHandler {
Log.d(TAG, "once/Eventsource: $eventSource")
// ignore fails from a possible old eventSource
// if we are already reconnected
if (this.eventSource == null || this.eventSource == eventSource) {
if (isRightEventSource(eventSource)) {
Log.d(TAG, "EventSource is known or null")
nFails = 1
this.eventSource = null
nFails.set(1)
this.eventSource.getAndSet(null)?.cancel()
} else {
eventSource?.cancel()
}
@ -79,27 +77,28 @@ object FailureHandler {
fun setMaxFails(context: Context) {
// We set nFails to max to not restart the worker
// and keep it running
nFails = 5
eventSource = null
nFails.set(5)
ttlFails.getAndIncrement()
this.eventSource.getAndSet(null)?.cancel()
DisconnectedNotification(context).show()
}
fun clearFails() {
ttlFails = 0
nFails = 0
nFailsBeforePing = 0
eventSource = null
ttlFails.set(0)
nFails.set(0)
nFailsBeforePing.set(0)
this.eventSource.getAndSet(null)?.cancel()
}
fun hasFailed(orNeverStart: Boolean = true): Boolean {
// nFails > 0 to be sure it is not actually restarting
return if (orNeverStart) { eventSource == null } else { false } || nFails > 0
return if (orNeverStart) { eventSource.get() == null } else { false } || nFails.get() > 0
}
fun getDebugInfo(): String {
return "ttlFails: $ttlFails\n" +
return "ttlFails: ${ttlFails.get()}\n" +
"nFails: $nFails\n" +
"nFailsBeforePing: $nFailsBeforePing\n" +
"eventSource null: ${eventSource == null}"
"eventSource null: ${eventSource.get() == null}"
}
}

View File

@ -6,11 +6,14 @@ import android.net.ConnectivityManager
import android.net.Network
import android.net.NetworkCapabilities
import android.util.Log
import org.unifiedpush.distributor.nextpush.AppCompanion
import org.unifiedpush.distributor.nextpush.utils.TAG
import java.lang.Exception
import java.util.concurrent.atomic.AtomicBoolean
import java.util.concurrent.atomic.AtomicReference
class RestartNetworkCallback(val context: Context) : ConnectivityManager.NetworkCallback() {
private var connectivityManager: ConnectivityManager? = null
private val connectivityManager: AtomicReference<ConnectivityManager?> = AtomicReference(null)
override fun onAvailable(network: Network) {
Log.d(TAG, "Network is CONNECTED")
@ -25,8 +28,7 @@ class RestartNetworkCallback(val context: Context) : ConnectivityManager.Network
networkCapabilities: NetworkCapabilities
) {
if (networkCapabilities.hasCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET)) {
if (!hasInternet) {
hasInternet = true
if (AppCompanion.hasInternet.getAndSet(true)) {
Log.d(TAG, "Network Capabilities changed")
if (FailureHandler.hasFailed(orNeverStart = false)) {
Log.d(TAG, "Internet Cap: restarting worker")
@ -38,22 +40,21 @@ class RestartNetworkCallback(val context: Context) : ConnectivityManager.Network
override fun onLost(network: Network) {
Log.d(TAG, "Network unavailable")
hasInternet = false
AppCompanion.hasInternet.set(false)
}
fun register() {
if (!registered) {
registered = true
connectivityManager?.let {
if (!registered.getAndSet(true)) {
connectivityManager.get()?.let {
Log.d(TAG, "ConnectivityManager already registered")
} ?: run {
Log.d(TAG, "Registering new ConnectivityManager")
try {
connectivityManager = (
connectivityManager.set((
context.getSystemService(Service.CONNECTIVITY_SERVICE) as ConnectivityManager
).apply {
registerDefaultNetworkCallback(this@RestartNetworkCallback)
}
})
} catch (e: Exception) {
e.printStackTrace()
}
@ -63,15 +64,12 @@ class RestartNetworkCallback(val context: Context) : ConnectivityManager.Network
fun unregister() {
Log.d(TAG, "Unregistering ConnectivityManager")
connectivityManager?.unregisterNetworkCallback(this)
connectivityManager = null
registered = false
hasInternet = false
connectivityManager.getAndSet(null)?.unregisterNetworkCallback(this)
registered.set(false)
AppCompanion.hasInternet.set(false)
}
companion object {
private var registered = false
var hasInternet = false
private set
private val registered = AtomicBoolean(false)
}
}

View File

@ -3,9 +3,8 @@ package org.unifiedpush.distributor.nextpush.services
import android.content.Context
import android.util.Log
import androidx.work.* // ktlint-disable no-wildcard-imports
import org.unifiedpush.distributor.nextpush.AppCompanion
import org.unifiedpush.distributor.nextpush.account.Account.getAccount
import org.unifiedpush.distributor.nextpush.api.SSEListener.Companion.keepalive
import org.unifiedpush.distributor.nextpush.api.SSEListener.Companion.lastEventDate
import org.unifiedpush.distributor.nextpush.utils.TAG
import java.util.Calendar
import java.util.concurrent.TimeUnit
@ -19,9 +18,9 @@ class RestartWorker(ctx: Context, params: WorkerParameters) : Worker(ctx, params
Log.d(TAG, "Working")
val currentDate = Calendar.getInstance()
val restartDate = Calendar.getInstance()
lastEventDate?.let {
AppCompanion.lastEventDate?.let {
restartDate.time = it.time
restartDate.add(Calendar.SECOND, keepalive)
restartDate.add(Calendar.SECOND, AppCompanion.keepalive.get())
Log.d(TAG, "restartDate: ${restartDate.time}")
if (currentDate.after(restartDate)) {
Log.d(TAG, "Restarting")
@ -51,7 +50,7 @@ class RestartWorker(ctx: Context, params: WorkerParameters) : Worker(ctx, params
val work = OneTimeWorkRequestBuilder<RestartWorker>().apply {
setInitialDelay(delay, TimeUnit.SECONDS)
}
lastEventDate = null
AppCompanion.lastEventDate = null
WorkManager.getInstance(context).enqueueUniqueWork(
UNIQUE_ONETIME_WORK_TAG,
ExistingWorkPolicy.REPLACE,

View File

@ -7,12 +7,13 @@ import android.os.Build
import android.os.IBinder
import android.os.PowerManager
import android.util.Log
import org.unifiedpush.distributor.nextpush.AppCompanion
import org.unifiedpush.distributor.nextpush.account.Account.getAccount
import org.unifiedpush.distributor.nextpush.api.Api
import org.unifiedpush.distributor.nextpush.api.SSEListener.Companion.lastEventDate
import org.unifiedpush.distributor.nextpush.utils.ForegroundNotification
import org.unifiedpush.distributor.nextpush.utils.NOTIFICATION_ID_FOREGROUND
import org.unifiedpush.distributor.nextpush.utils.TAG
import java.util.concurrent.atomic.AtomicReference
class StartService : Service() {
@ -25,7 +26,7 @@ class StartService : Service() {
override fun onCreate() {
super.onCreate()
service = this
service.set(this)
Log.i(TAG, "StartService created")
val notification = ForegroundNotification(this).create()
startForeground(NOTIFICATION_ID_FOREGROUND, notification)
@ -80,18 +81,17 @@ class StartService : Service() {
companion object StartServiceCompanion {
private const val WAKE_LOCK_TAG = "NextPush:StartService:lock"
private var service: StartService? = null
private val service: AtomicReference<StartService?> = AtomicReference(null)
var isServiceStarted = false
private set
var wakeLock: PowerManager.WakeLock? = null
private set
var bufferedResponseChecked = false
fun startListener(context: Context) {
if (isServiceStarted && !FailureHandler.hasFailed()) return
Log.d(TAG, "Starting the Listener")
Log.d(TAG, "Service is started: $isServiceStarted")
Log.d(TAG, "nFails: ${FailureHandler.nFails}")
Log.d(TAG, "nFails: ${FailureHandler.nFails()}")
val serviceIntent = Intent(context, StartService::class.java)
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
context.startForegroundService(serviceIntent)
@ -103,9 +103,9 @@ class StartService : Service() {
fun stopService(block: () -> Unit = {}) {
Log.d(TAG, "Stopping Service")
isServiceStarted = false
bufferedResponseChecked = false
lastEventDate = null
service?.stopSelf()
AppCompanion.bufferedResponseChecked.set(false)
AppCompanion.lastEventDate = null
service.get()?.stopSelf()
block()
}
}

View File

@ -1,18 +1,18 @@
package org.unifiedpush.distributor.nextpush.utils
import org.unifiedpush.distributor.nextpush.api.SSEListener
import org.unifiedpush.distributor.nextpush.AppCompanion
import org.unifiedpush.distributor.nextpush.services.FailureHandler
import org.unifiedpush.distributor.nextpush.services.StartService
import java.text.SimpleDateFormat
fun getDebugInfo(): String {
val date = SSEListener.lastEventDate?.let {
val date = AppCompanion.lastEventDate?.let {
SimpleDateFormat.getDateTimeInstance().format(it.time)
} ?: "None"
return "ServiceStarted: ${StartService.isServiceStarted}\n" +
"Last Event: $date\n" +
"Keepalive: ${SSEListener.keepalive}\n" +
"SSE started: ${SSEListener.started}\n" +
"SSE pinged: ${SSEListener.pinged}\n" +
"Keepalive: ${AppCompanion.keepalive.get()}\n" +
"SSE started: ${AppCompanion.started}\n" +
"SSE pinged: ${AppCompanion.pinged}\n" +
FailureHandler.getDebugInfo()
}