Skip to content
This repository was archived by the owner on Dec 18, 2022. It is now read-only.

Commit 4b14116

Browse files
committed
finalize BackgroundManager's new option to run service in Foreground while application is backgrounded
1 parent bf42bf3 commit 4b14116

File tree

5 files changed

+81
-57
lines changed

5 files changed

+81
-57
lines changed

sampleapp/src/main/java/io/matthewnelson/sampleapp/App.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -124,7 +124,7 @@ class App: Application() {
124124

125125
// All available options present. Only 1 is able to be chosen.
126126
//.respectResourcesWhileInBackground(secondsFrom5To45 = 20)
127-
.runServiceInForeground(secondsFrom0To45 = 0)
127+
.runServiceInForeground(killAppIfTaskIsRemoved = true)
128128
// }
129129
}
130130

topl-service/src/main/java/io/matthewnelson/topl_service/lifecycle/BackgroundManager.kt

Lines changed: 76 additions & 54 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,7 @@
6767
package io.matthewnelson.topl_service.lifecycle
6868

6969
import android.content.Context
70+
import android.os.Process
7071
import androidx.lifecycle.Lifecycle
7172
import androidx.lifecycle.LifecycleObserver
7273
import 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.

topl-service/src/main/java/io/matthewnelson/topl_service/service/BaseService.kt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,7 @@ import io.matthewnelson.topl_core.broadcaster.BroadcastLogger
7676
import io.matthewnelson.topl_core_base.TorConfigFiles
7777
import io.matthewnelson.topl_core_base.TorSettings
7878
import io.matthewnelson.topl_service.BuildConfig
79+
import io.matthewnelson.topl_service.lifecycle.BackgroundManager
7980
import io.matthewnelson.topl_service.notification.ServiceNotification
8081
import io.matthewnelson.topl_service.prefs.TorServicePrefsListener
8182
import io.matthewnelson.topl_service.service.components.actions.ServiceActionProcessor
@@ -377,6 +378,7 @@ internal abstract class BaseService: Service() {
377378

378379

379380
override fun onTaskRemoved(rootIntent: Intent?) {
381+
BackgroundManager.taskIsRemovedFromRecentApps(true)
380382
// Move to the foreground so we can properly shutdown w/o interrupting the
381383
// application's normal lifecycle (Context.startServiceForeground does... thus,
382384
// the complexity)

topl-service/src/main/java/io/matthewnelson/topl_service/service/TorService.kt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,7 @@ import io.matthewnelson.topl_core.OnionProxyManager
7373
import io.matthewnelson.topl_core.broadcaster.BroadcastLogger
7474
import io.matthewnelson.topl_core.util.FileUtilities
7575
import io.matthewnelson.topl_service.TorServiceController
76+
import io.matthewnelson.topl_service.lifecycle.BackgroundManager
7677
import io.matthewnelson.topl_service.service.components.binding.TorServiceBinder
7778
import io.matthewnelson.topl_service.service.components.binding.TorServiceConnection
7879
import io.matthewnelson.topl_service.service.components.onionproxy.ServiceEventBroadcaster
@@ -258,6 +259,7 @@ internal class TorService: BaseService() {
258259
super.onDestroy()
259260
supervisorJob.cancel()
260261
removeNotification()
262+
BackgroundManager.killAppProcess()
261263
}
262264

263265
override fun onTaskRemoved(rootIntent: Intent?) {

topl-service/src/main/java/io/matthewnelson/topl_service/service/components/binding/BaseServiceBinder.kt

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -118,8 +118,6 @@ internal abstract class BaseServiceBinder(private val torService: BaseService):
118118
backgroundPolicyExecutionJob = torService.getScopeMain().launch {
119119
when (policy) {
120120
BackgroundPolicy.RUN_IN_FOREGROUND -> {
121-
if (executionDelay > 0)
122-
delay(executionDelay)
123121
bgMgrBroadcastLogger.debug("Executing background management policy")
124122
torService.startForegroundService()
125123
}

0 commit comments

Comments
 (0)