Use a global companion
This commit is contained in:
parent
ded36d2e6e
commit
2ba1d35c66
|
@ -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
|
||||
}
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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}"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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()
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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()
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue