Signal-Android/app/src/main/java/org/thoughtcrime/securesms/gcm/FcmFetchManager.kt

115 wiersze
4.0 KiB
Kotlin

package org.thoughtcrime.securesms.gcm
import android.content.Context
import android.content.Intent
import android.os.Build
import org.signal.core.util.concurrent.SignalExecutors
import org.signal.core.util.logging.Log
import org.thoughtcrime.securesms.dependencies.ApplicationDependencies
import org.thoughtcrime.securesms.jobs.ForegroundServiceUtil
import org.thoughtcrime.securesms.jobs.PushNotificationReceiveJob
import org.thoughtcrime.securesms.messages.RestStrategy
import org.thoughtcrime.securesms.util.concurrent.SerialMonoLifoExecutor
/**
* Our goals with FCM processing are as follows:
* (1) Ensure some service is active for the duration of the fetch and processing stages.
* (2) Do not make unnecessary network requests.
*
* To fulfill goal 1, this class will not stop the services until there is no more running
* requests.
*
* To fulfill goal 2, this class will not enqueue a fetch if there are already 2 active fetches
* (or rather, 1 active and 1 waiting, since we use a single thread executor).
*
* Unfortunately we can't do this all in [FcmReceiveService] because it won't let us process
* the next FCM message until [FcmReceiveService.onMessageReceived] returns,
* but as soon as that method returns, it could also destroy the service. By not letting us control
* when the service is destroyed, we can't accomplish both goals within that service.
*/
object FcmFetchManager {
private val TAG = Log.tag(FcmFetchManager::class.java)
private val EXECUTOR = SerialMonoLifoExecutor(SignalExecutors.UNBOUNDED)
@Volatile
private var activeCount = 0
@Volatile
private var startedForeground = false
/**
* @return True if a service was successfully started, otherwise false.
*/
@JvmStatic
fun enqueue(context: Context, foreground: Boolean): Boolean {
synchronized(this) {
try {
if (foreground) {
Log.i(TAG, "Starting in the foreground.")
ForegroundServiceUtil.startWhenCapableOrThrow(context, Intent(context, FcmFetchForegroundService::class.java))
startedForeground = true
} else {
Log.i(TAG, "Starting in the background.")
context.startService(Intent(context, FcmFetchBackgroundService::class.java))
}
val performedReplace = EXECUTOR.enqueue { fetch(context) }
if (performedReplace) {
Log.i(TAG, "Already have one running and one enqueued. Ignoring.")
} else {
activeCount++
Log.i(TAG, "Incrementing active count to $activeCount")
}
} catch (e: Exception) {
Log.w(TAG, "Failed to start service!", e)
return false
}
}
return true
}
private fun fetch(context: Context) {
retrieveMessages(context)
synchronized(this) {
activeCount--
if (activeCount <= 0) {
Log.i(TAG, "No more active. Stopping.")
context.stopService(Intent(context, FcmFetchBackgroundService::class.java))
if (startedForeground) {
try {
context.startService(FcmFetchForegroundService.buildStopIntent(context))
} catch (e: IllegalStateException) {
Log.w(TAG, "Failed to stop the foreground notification!", e)
}
startedForeground = false
}
}
}
}
@JvmStatic
fun retrieveMessages(context: Context) {
val success = ApplicationDependencies.getBackgroundMessageRetriever().retrieveMessages(context, RestStrategy(), RestStrategy())
if (success) {
Log.i(TAG, "Successfully retrieved messages.")
} else {
if (Build.VERSION.SDK_INT >= 26) {
Log.w(TAG, "[API ${Build.VERSION.SDK_INT}] Failed to retrieve messages. Scheduling on the system JobScheduler (API " + Build.VERSION.SDK_INT + ").")
FcmJobService.schedule(context)
} else {
Log.w(TAG, "[API ${Build.VERSION.SDK_INT}] Failed to retrieve messages. Scheduling on JobManager (API " + Build.VERSION.SDK_INT + ").")
ApplicationDependencies.getJobManager().add(PushNotificationReceiveJob())
}
}
}
}