@ -4,9 +4,11 @@ import android.content.BroadcastReceiver
import android.content.Context
import android.content.Context
import android.content.Intent
import android.content.Intent
import androidx.work.Constraints
import androidx.work.Constraints
import androidx.work.Data
import androidx.work.ExistingPeriodicWorkPolicy
import androidx.work.ExistingPeriodicWorkPolicy
import androidx.work.NetworkType
import androidx.work.NetworkType
import androidx.work.PeriodicWorkRequestBuilder
import androidx.work.PeriodicWorkRequestBuilder
import androidx.work.OneTimeWorkRequestBuilder
import androidx.work.WorkManager
import androidx.work.WorkManager
import androidx.work.Worker
import androidx.work.Worker
import androidx.work.WorkerParameters
import androidx.work.WorkerParameters
@ -21,19 +23,35 @@ import org.session.libsession.messaging.sending_receiving.pollers.OpenGroupPolle
import org.session.libsession.snode.SnodeAPI
import org.session.libsession.snode.SnodeAPI
import org.session.libsession.utilities.TextSecurePreferences
import org.session.libsession.utilities.TextSecurePreferences
import org.session.libsignal.utilities.Log
import org.session.libsignal.utilities.Log
import org.session.libsignal.utilities.recover
import org.thoughtcrime.securesms.dependencies.DatabaseComponent
import org.thoughtcrime.securesms.dependencies.DatabaseComponent
import java.util.concurrent.TimeUnit
import java.util.concurrent.TimeUnit
class BackgroundPollWorker ( val context : Context , params : WorkerParameters ) : Worker ( context , params ) {
class BackgroundPollWorker ( val context : Context , params : WorkerParameters ) : Worker ( context , params ) {
enum class Targets {
DMS , CLOSED _GROUPS , OPEN _GROUPS
}
companion object {
companion object {
const val TAG = " BackgroundPollWorker "
const val TAG = " BackgroundPollWorker "
const val INITIAL _SCHEDULE _TIME = " INITIAL_SCHEDULE_TIME "
const val REQUEST _TARGETS = " REQUEST_TARGETS "
@JvmStatic
fun schedulePeriodic ( context : Context ) = schedulePeriodic ( context , targets = Targets . values ( ) )
@JvmStatic
@JvmStatic
fun schedulePeriodic ( context : Context ) {
fun schedulePeriodic ( context : Context , targets : Array < Targets > ) {
Log . v ( TAG , " Scheduling periodic work. " )
Log . v ( TAG , " Scheduling periodic work. " )
val builder = PeriodicWorkRequestBuilder < BackgroundPollWorker > ( 15 , TimeUnit . MINUTES )
val durationMinutes : Long = 15
val builder = PeriodicWorkRequestBuilder < BackgroundPollWorker > ( durationMinutes , TimeUnit . MINUTES )
builder . setConstraints ( Constraints . Builder ( ) . setRequiredNetworkType ( NetworkType . CONNECTED ) . build ( ) )
builder . setConstraints ( Constraints . Builder ( ) . setRequiredNetworkType ( NetworkType . CONNECTED ) . build ( ) )
val dataBuilder = Data . Builder ( )
dataBuilder . putLong ( INITIAL _SCHEDULE _TIME , System . currentTimeMillis ( ) + ( durationMinutes * 60 * 1000 ) )
dataBuilder . putStringArray ( REQUEST _TARGETS , targets . map { it . name } . toTypedArray ( ) )
builder . setInputData ( dataBuilder . build ( ) )
val workRequest = builder . build ( )
val workRequest = builder . build ( )
WorkManager . getInstance ( context ) . enqueueUniquePeriodicWork (
WorkManager . getInstance ( context ) . enqueueUniquePeriodicWork (
TAG ,
TAG ,
@ -41,6 +59,20 @@ class BackgroundPollWorker(val context: Context, params: WorkerParameters) : Wor
workRequest
workRequest
)
)
}
}
@JvmStatic
fun scheduleOnce ( context : Context , targets : Array < Targets > = Targets . values ( ) ) {
Log . v ( TAG , " Scheduling single run. " )
val builder = OneTimeWorkRequestBuilder < BackgroundPollWorker > ( )
builder . setConstraints ( Constraints . Builder ( ) . setRequiredNetworkType ( NetworkType . CONNECTED ) . build ( ) )
val dataBuilder = Data . Builder ( )
dataBuilder . putStringArray ( REQUEST _TARGETS , targets . map { it . name } . toTypedArray ( ) )
builder . setInputData ( dataBuilder . build ( ) )
val workRequest = builder . build ( )
WorkManager . getInstance ( context ) . enqueue ( workRequest )
}
}
}
override fun doWork ( ) : Result {
override fun doWork ( ) : Result {
@ -49,41 +81,89 @@ class BackgroundPollWorker(val context: Context, params: WorkerParameters) : Wor
return Result . failure ( )
return Result . failure ( )
}
}
// If this is a scheduled run and it is happening before the initial scheduled time (as
// periodic background tasks run immediately when scheduled) then don't actually do anything
// because this might slow requests on initial startup or triggered by PNs
val initialScheduleTime = inputData . getLong ( INITIAL _SCHEDULE _TIME , - 1 )
if ( initialScheduleTime != - 1L && System . currentTimeMillis ( ) < ( initialScheduleTime - ( 60 * 1000 ) ) ) {
Log . v ( TAG , " Skipping initial run. " )
return Result . success ( )
}
// Retrieve the desired targets (defaulting to all if not provided or empty)
val requestTargets : List < Targets > = ( inputData . getStringArray ( REQUEST _TARGETS ) ?: emptyArray ( ) )
. map {
try { Targets . valueOf ( it ) }
catch ( e : Exception ) { null }
}
. filterNotNull ( )
. ifEmpty { Targets . values ( ) . toList ( ) }
try {
try {
Log . v ( TAG , " Performing background poll. " )
Log . v ( TAG , " Performing background poll for ${requestTargets.joinToString { it.name } } ." )
val promises = mutableListOf < Promise < Unit , Exception > > ( )
val promises = mutableListOf < Promise < Unit , Exception > > ( )
// DMs
// DMs
val userPublicKey = TextSecurePreferences . getLocalNumber ( context ) !!
var dmsPromise : Promise < Unit , Exception > = Promise . ofSuccess ( Unit )
val dmsPromise = SnodeAPI . getMessages ( userPublicKey ) . bind { envelopes ->
val params = envelopes . map { ( envelope , serverHash ) ->
if ( requestTargets . contains ( Targets . DMS ) ) {
// FIXME: Using a job here seems like a bad idea...
val userPublicKey = TextSecurePreferences . getLocalNumber ( context ) !!
MessageReceiveParameters ( envelope . toByteArray ( ) , serverHash , null )
dmsPromise = SnodeAPI . getMessages ( userPublicKey ) . bind { envelopes ->
val params = envelopes . map { ( envelope , serverHash ) ->
// FIXME: Using a job here seems like a bad idea...
MessageReceiveParameters ( envelope . toByteArray ( ) , serverHash , null )
}
BatchMessageReceiveJob ( params ) . executeAsync ( " background " )
}
}
BatchMessageReceiveJob ( params ) . executeAsync ( " background " )
promises. add ( dmsPromise )
}
}
promises . add ( dmsPromise )
// Closed groups
// Closed groups
val closedGroupPoller = ClosedGroupPollerV2 ( ) // Intentionally don't use shared
if ( requestTargets . contains ( Targets . CLOSED _GROUPS ) ) {
val storage = MessagingModuleConfiguration . shared . storage
val closedGroupPoller = ClosedGroupPollerV2 ( ) // Intentionally don't use shared
val allGroupPublicKeys = storage . getAllClosedGroupPublicKeys ( )
val storage = MessagingModuleConfiguration . shared . storage
allGroupPublicKeys . iterator ( ) . forEach { closedGroupPoller . poll ( it ) }
val allGroupPublicKeys = storage . getAllClosedGroupPublicKeys ( )
allGroupPublicKeys . iterator ( ) . forEach { closedGroupPoller . poll ( it ) }
}
// Open Groups
// Open Groups
val threadDB = DatabaseComponent . get ( context ) . lokiThreadDatabase ( )
var ogPollError : Exception ? = null
val openGroups = threadDB . getAllOpenGroups ( )
val openGroupServers = openGroups . map { it . value . server } . toSet ( )
if ( requestTargets . contains ( Targets . OPEN _GROUPS ) ) {
val threadDB = DatabaseComponent . get ( context ) . lokiThreadDatabase ( )
for ( server in openGroupServers ) {
val openGroups = threadDB . getAllOpenGroups ( )
val poller = OpenGroupPoller ( server , null )
val openGroupServers = openGroups . map { it . value . server } . toSet ( )
poller . hasStarted = true
promises . add ( poller . poll ( ) )
for ( server in openGroupServers ) {
val poller = OpenGroupPoller ( server , null )
poller . hasStarted = true
// If one of the open group pollers fails we don't want it to cancel the DM
// poller so just hold on to the error for later
promises . add (
poller . poll ( ) . recover {
if ( dmsPromise . isDone ( ) ) {
throw it
}
ogPollError = it
}
)
}
}
}
// Wait until all the promises are resolved
// Wait until all the promises are resolved
all ( promises ) . get ( )
all ( promises ) . get ( )
// If the Open Group pollers threw an exception then re-throw it here (now that
// the DM promise has completed)
val localOgPollException = ogPollError
if ( localOgPollException != null ) {
throw localOgPollException
}
return Result . success ( )
return Result . success ( )
} catch ( exception : Exception ) {
} catch ( exception : Exception ) {
Log . e ( TAG , " Background poll failed due to error: ${exception.message} . " , exception )
Log . e ( TAG , " Background poll failed due to error: ${exception.message} . " , exception )