6767package io.matthewnelson.topl_service.lifecycle
6868
6969import android.content.Context
70+ import android.os.Process
7071import androidx.lifecycle.Lifecycle
7172import androidx.lifecycle.LifecycleObserver
7273import androidx.lifecycle.OnLifecycleEvent
@@ -79,20 +80,22 @@ import io.matthewnelson.topl_service.util.ServiceConsts
7980
8081/* *
8182 * When your application is sent to the background (the Recent App's tray or lock screen), the
82- * chosen [BackgroundManager.Builder.Policy] will be executed after the number of seconds you've
83- * declared.
83+ * chosen [BackgroundManager.Builder.Policy] will be triggered.
84+ *
85+ * Additionally, there are 2 values for you to query if needed to give you context surrounding
86+ * your application's background state; [taskIsInForeground] and [taskIsRemovedFromRecentApps].
8487 *
8588 * If brought back into the foreground by the user:
8689 *
8790 * - **Before Policy execution**: Execution is canceled. If [BaseService.lastAcceptedServiceAction]
8891 * was **not** [ServiceConsts.ServiceActionName.STOP], a startService call is made to ensure it's
8992 * started.
9093 *
91- * - **After Policy execution**: If [BaseService.lastAcceptedServiceAction]
92- * was **not** [ServiceConsts.ServiceActionName.STOP], a startService call is made to ensure it's
93- * started.
94+ * - **After Policy execution**: If [BaseService.lastAcceptedServiceAction] was **not**
95+ * [ServiceConsts.ServiceActionName.STOP], a startService call is made to ensure it's started.
9496 *
95- * - See [BaseService.updateLastAcceptedServiceAction] and [TorService.onTaskRemoved] for
97+ * - See [io.matthewnelson.topl_service.service.components.binding.BaseServiceBinder],
98+ * [BaseService.updateLastAcceptedServiceAction], and [TorService.onTaskRemoved] for
9699 * more information.
97100 *
98101 * While your application is in the foreground the only way to stop the service is by
@@ -103,15 +106,16 @@ import io.matthewnelson.topl_service.util.ServiceConsts
103106 *
104107 * When the user sends your application to the Recent App's tray though, to recoup resources
105108 * the OS will kill your app after being idle for a period of time (random AF... typically
106- * 0.75m to 1.25m if the device's memory is being used heavily). This is not an issue if
107- * the user removes the task before the OS kills the app, as Tor will be able to shutdown
108- * properly and the service will stop.
109+ * 0.75m to 1.25m if the device's memory is being used heavily) if the service is not moved to
110+ * the Foreground to inhibit this. This is not an issue if the user removes the task before the
111+ * OS kills the app, as Tor will be able to shutdown properly and the service will stop.
109112 *
110113 * This is where Services get sketchy (especially when trying to implement an always
111114 * running service for networking), and is the purpose of the [BackgroundManager] class.
112115 *
113116 * This class starts your chosen [BackgroundManager.Builder.Policy] as soon as your
114- * application is sent to the background, waits for the time you declared, and then executes.
117+ * application is sent to the background. It facilitates a more declarative, flexible
118+ * operation to fit Library users' needs.
115119 *
116120 * See the [BackgroundManager.Builder] for more detail.
117121 *
@@ -127,7 +131,8 @@ class BackgroundManager internal constructor(
127131 @BackgroundPolicy private val policy : String ,
128132 private val executionDelay : Long ,
129133 private val serviceClass : Class <* >,
130- private val bindServiceFlag : Int
134+ private val bindServiceFlag : Int ,
135+ private val killAppIfTaskIsRemoved : Boolean
131136): ServiceConsts(), LifecycleObserver {
132137
133138
@@ -148,29 +153,7 @@ class BackgroundManager internal constructor(
148153 @BackgroundPolicy
149154 private lateinit var chosenPolicy: String
150155 private var executionDelay: Long = 30_000L
151-
152- // TODO: Needs more work... running in the foreground is inhibiting the Application from
153- // performing it's normal lifecycle after user swipes it away such that it's not going
154- // through Application.onCreate, but is holding onto references. (same problem when
155- // starting the service using Context.startForegroundService), which is bullshit.
156- // /**
157- // * While your application is in the background (the Recent App's tray or lock screen),
158- // * this [Policy] periodically switches [TorService] to the foreground then immediately
159- // * back the background. Doing do prevents your application from going idle and being
160- // * killed by the OS. It is much more resource intensive than choosing
161- // * [respectResourcesWhileInBackground].
162- // *
163- // * @param [secondsFrom20To40]? Seconds between the events of cycling from background to
164- // * foreground to background. Sending null will use the default (30s)
165- // * @return [BackgroundManager.Builder.Policy] To use when initializing
166- // * [io.matthewnelson.topl_service.TorServiceController.Builder]
167- // * */
168- // fun keepAliveWhileInBackground(secondsFrom20To40: Int? = null): Policy {
169- // chosenPolicy = BackgroundPolicy.KEEP_ALIVE
170- // if (secondsFrom20To40 != null && secondsFrom20To40 in 20..40)
171- // executionDelay = (secondsFrom20To40 * 1000).toLong()
172- // return Policy(this)
173- // }
156+ private var killAppIfTaskIsRemoved = false
174157
175158 /* *
176159 * Stops [TorService] after being in the background for the declared [secondsFrom5To45].
@@ -202,18 +185,33 @@ class BackgroundManager internal constructor(
202185 }
203186
204187 /* *
205- * Electing this option will simply run [TorService] while your application is
206- * in the background, until the OS kills it along with your application. As long
207- * as your application has not been killed, the service will keep running. Upon
208- * being killed, the notification (if enabled) will simply timeout and cancel itself.
209- * All processes and threads will stop, and a cold start called on your application
210- * the next time the user opens it.
188+ * Electing this option will, when your application is sent to the background, immediately
189+ * move [TorService] to the Foreground. If the user returns to your application,
190+ * [TorService] will then be backgrounded.
191+ *
192+ * Some things to note about your application's behaviour with this option:
193+ *
194+ * - If the user sends your app to the recent App's tray and then swipes it away,
195+ * [TorService.onTaskRemoved] will stop Tor, and then [TorService].
196+ * - Because of how shitty the Service APIs are, your application will _not_ be
197+ * killed like one would expect, thus not going through `Application.onCreate` if the
198+ * user re-launches your application.
199+ * - In the event of being re-launched in the aforementioned state,
200+ * [applicationMovedToForeground] is called and Tor will be started again to match the
201+ * Service's State for which it was left, prior to "terminating" your application.
202+ * - Even while the Service has been properly stopped and everything cleaned up, your
203+ * application will continue running and not be killed (Again, Service APIs...).
204+ * - If [TorService] is stopped, and *then* your application is cleared from the
205+ * recent apps tray, your application will be killed.
206+ *
207+ * @param [killAppIfTaskIsRemoved] If set to `true`, your Application's Process will be
208+ * killed in [TorService.onDestroy] if the user removed the task from the recent app's
209+ * tray and has not returned to the application before [killAppProcess] is called.
211210 * */
212211 @JvmOverloads
213- fun runServiceInForeground (secondsFrom0To45 : Int? = null ): Policy {
212+ fun runServiceInForeground (killAppIfTaskIsRemoved : Boolean = false ): Policy {
214213 chosenPolicy = BackgroundPolicy .RUN_IN_FOREGROUND
215- if (secondsFrom0To45 != null && secondsFrom0To45 in 0 .. 45 )
216- executionDelay = (secondsFrom0To45 * 1000 ).toLong()
214+ this .killAppIfTaskIsRemoved = killAppIfTaskIsRemoved
217215 return Policy (this )
218216 }
219217
@@ -246,25 +244,46 @@ class BackgroundManager internal constructor(
246244 policyBuilder.chosenPolicy,
247245 policyBuilder.executionDelay,
248246 serviceClass,
249- bindServiceFlag
247+ bindServiceFlag,
248+ policyBuilder.killAppIfTaskIsRemoved
250249 )
251250 }
252251 }
253252 }
254253 }
255254
256- internal companion object {
255+ companion object {
257256 private lateinit var backgroundManager: BackgroundManager
258257
259- // TODO: re-implement in BaseService as a monitor for Tor's state so it can automatically
260- // handle hiccups (such as network getting stuck b/c Android is sometimes unreliable,
261- // or Bootstrapping stalling).
262- // var heartbeatTime = 30_000L
263- // private set
264- //
265- // fun initialize(milliseconds: Long) {
266- // heartbeatTime = milliseconds
267- // }
258+ @JvmStatic
259+ @Volatile
260+ var taskIsInForeground = true
261+ private set
262+
263+ @JvmStatic
264+ @Volatile
265+ var taskIsRemovedFromRecentApps = false
266+ private set
267+
268+ internal fun taskIsRemovedFromRecentApps (isRemoved : Boolean ) {
269+ taskIsRemovedFromRecentApps = isRemoved
270+ }
271+
272+ /* *
273+ * Called from [TorService.onDestroy].
274+ *
275+ * Will only kill the application process if [taskIsRemovedFromRecentApps] is `true`.
276+ * [taskIsRemovedFromRecentApps] will turn back to `false` when
277+ * [applicationMovedToForeground] is fired off, as to not kill the application in the
278+ * event the user quickly re-launches the application between the time
279+ * [TorService.onTaskRemoved] gets a callback, and this method is called from
280+ * [TorService.onDestroy].
281+ * */
282+ internal fun killAppProcess () {
283+ if (backgroundManager.killAppIfTaskIsRemoved && taskIsRemovedFromRecentApps) {
284+ Process .killProcess(Process .myPid())
285+ }
286+ }
268287 }
269288
270289 init {
@@ -273,6 +292,8 @@ class BackgroundManager internal constructor(
273292
274293 @OnLifecycleEvent(Lifecycle .Event .ON_START )
275294 private fun applicationMovedToForeground () {
295+ taskIsRemovedFromRecentApps(false )
296+ taskIsInForeground = true
276297 // if the last _accepted_ ServiceAction to be issued by the Application was not to STOP
277298 // the service, then we want to put it back in the state it was in
278299 if (! ServiceActionProcessor .wasLastAcceptedServiceActionStop()) {
@@ -288,6 +309,7 @@ class BackgroundManager internal constructor(
288309
289310 @OnLifecycleEvent(Lifecycle .Event .ON_STOP )
290311 private fun applicationMovedToBackground () {
312+ taskIsInForeground = false
291313 if (! ServiceActionProcessor .wasLastAcceptedServiceActionStop()) {
292314 // System automatically unbinds when app is sent to the background. This prevents
293315 // it so that we maintain a started, bound service.
0 commit comments