@ -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,13 +81,35 @@ 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
var dmsPromise : Promise < Unit , Exception > = Promise . ofSuccess ( Unit )
if ( requestTargets . contains ( Targets . DMS ) ) {
val userPublicKey = TextSecurePreferences . getLocalNumber ( context ) !!
val userPublicKey = TextSecurePreferences . getLocalNumber ( context ) !!
val dmsPromise = SnodeAPI . getMessages ( userPublicKey ) . bind { envelopes ->
dmsPromise = SnodeAPI . getMessages ( userPublicKey ) . bind { envelopes ->
val params = envelopes . map { ( envelope , serverHash ) ->
val params = envelopes . map { ( envelope , serverHash ) ->
// FIXME: Using a job here seems like a bad idea...
// FIXME: Using a job here seems like a bad idea...
MessageReceiveParameters ( envelope . toByteArray ( ) , serverHash , null )
MessageReceiveParameters ( envelope . toByteArray ( ) , serverHash , null )
@ -63,14 +117,20 @@ class BackgroundPollWorker(val context: Context, params: WorkerParameters) : Wor
BatchMessageReceiveJob ( params ) . executeAsync ( " background " )
BatchMessageReceiveJob ( params ) . executeAsync ( " background " )
}
}
promises . add ( dmsPromise )
promises . add ( dmsPromise )
}
// Closed groups
// Closed groups
if ( requestTargets . contains ( Targets . CLOSED _GROUPS ) ) {
val closedGroupPoller = ClosedGroupPollerV2 ( ) // Intentionally don't use shared
val closedGroupPoller = ClosedGroupPollerV2 ( ) // Intentionally don't use shared
val storage = MessagingModuleConfiguration . shared . storage
val storage = MessagingModuleConfiguration . shared . storage
val allGroupPublicKeys = storage . getAllClosedGroupPublicKeys ( )
val allGroupPublicKeys = storage . getAllClosedGroupPublicKeys ( )
allGroupPublicKeys . iterator ( ) . forEach { closedGroupPoller . poll ( it ) }
allGroupPublicKeys . iterator ( ) . forEach { closedGroupPoller . poll ( it ) }
}
// Open Groups
// Open Groups
var ogPollError : Exception ? = null
if ( requestTargets . contains ( Targets . OPEN _GROUPS ) ) {
val threadDB = DatabaseComponent . get ( context ) . lokiThreadDatabase ( )
val threadDB = DatabaseComponent . get ( context ) . lokiThreadDatabase ( )
val openGroups = threadDB . getAllOpenGroups ( )
val openGroups = threadDB . getAllOpenGroups ( )
val openGroupServers = openGroups . map { it . value . server } . toSet ( )
val openGroupServers = openGroups . map { it . value . server } . toSet ( )
@ -78,12 +138,32 @@ class BackgroundPollWorker(val context: Context, params: WorkerParameters) : Wor
for ( server in openGroupServers ) {
for ( server in openGroupServers ) {
val poller = OpenGroupPoller ( server , null )
val poller = OpenGroupPoller ( server , null )
poller . hasStarted = true
poller . hasStarted = true
promises . add ( poller . poll ( ) )
// 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 )