@@ -6,6 +6,8 @@ import android.content.BroadcastReceiver
66import android.content.Context
77import android.content.Intent
88import android.content.IntentFilter
9+ import android.os.Handler
10+ import android.os.HandlerThread
911import android.os.IBinder
1012import androidx.core.content.ContextCompat
1113import com.mg4.hardware.AppLogger
@@ -15,6 +17,7 @@ import com.mg4.hardware.VehicleWriteGate
1517import com.mg4.tasker.model.RuleTrigger
1618import com.mg4.tasker.store.AppState
1719import com.mg4.tasker.util.Notifier
20+ import com.mg4.tasker.vehicle.BtOnboard
1821import com.mg4.tasker.vehicle.BtTracker
1922import com.mg4.tasker.vehicle.DeferredWrites
2023import com.mg4.tasker.vehicle.ProfileBridge
@@ -36,6 +39,14 @@ class TaskerVehicleService : Service() {
3639
3740 companion object {
3841 private const val TAG = " MG4Tasker.Vehicle"
42+ /* *
43+ * How often the moving car is asked which phones are still with it.
44+ *
45+ * Two binder reads (speed, connected devices) — cheap enough at this pace that the
46+ * drive is sampled several times before the first junction, and rare enough that a
47+ * long motorway leg costs nothing measurable.
48+ */
49+ private const val ONBOARD_SAMPLE_MS = 15_000L
3950 private const val HARDKEY_ACTION = " com.saic.keyevent.hardkey.report"
4051 private const val SYSTEMUI_HARDKEY_ACTION = " com.android.systemui.ACTION_HARD_KEY_EVENT"
4152 const val HARDKEY_PERMISSION = " com.mg4.tasker.permission.RECEIVE_HARDKEY"
@@ -65,6 +76,19 @@ class TaskerVehicleService : Service() {
6576 @Volatile
6677 private var lastTrigger: RuleTrigger ? = null
6778
79+ /* *
80+ * Off the main thread: sampling reads the car and the Bluetooth stack over binder, and
81+ * this service runs in the same process as the UI.
82+ */
83+ private val onboardThread = HandlerThread (" mg4-tasker-onboard" )
84+ private lateinit var onboardHandler: Handler
85+ private val onboardSampler = object : Runnable {
86+ override fun run () {
87+ BtOnboard .sample(this @TaskerVehicleService)
88+ onboardHandler.postDelayed(this , ONBOARD_SAMPLE_MS )
89+ }
90+ }
91+
6892 override fun onBind (intent : Intent ? ): IBinder ? = null
6993
7094 override fun onCreate () {
@@ -97,9 +121,17 @@ class TaskerVehicleService : Service() {
97121 // The gate resets to standstill-only on every process start; hand it back the
98122 // threshold the user chose before any rule can be evaluated.
99123 AppState .applyWriteThreshold(this )
124+ onboardThread.start()
125+ onboardHandler = Handler (onboardThread.looper)
100126 registerBtReceiver()
101127 registerHardkeyReceiver()
102128 registerIgnitionListener()
129+ // START_STICKY means this service can be recreated mid-drive. Waiting for the next
130+ // IGNITION_ON would then leave the whole drive unsampled, and every "phone on board"
131+ // rule unevaluable until the car had been switched off and on again.
132+ if (MG4Hardware .getCurrentIgnitionState() == MG4Hardware .CarIgnitionItem .RUN ) {
133+ startOnboardSampling()
134+ }
103135 warnIfMG4ControlPresent()
104136 isRunning = true
105137 }
@@ -112,6 +144,17 @@ class TaskerVehicleService : Service() {
112144 ignitionListener?.let { MG4Hardware .unregisterVehicleConditionListener(it) }
113145 btReceiver?.let { runCatching { unregisterReceiver(it) } }
114146 hardkeyReceiver?.let { runCatching { unregisterReceiver(it) } }
147+ onboardHandler.removeCallbacks(onboardSampler)
148+ onboardThread.quitSafely()
149+ }
150+
151+ private fun startOnboardSampling () {
152+ onboardHandler.removeCallbacks(onboardSampler)
153+ onboardHandler.post(onboardSampler)
154+ }
155+
156+ private fun stopOnboardSampling () {
157+ onboardHandler.removeCallbacks(onboardSampler)
115158 }
116159
117160 /* *
@@ -136,6 +179,15 @@ class TaskerVehicleService : Service() {
136179 // The drive is over: writes held back for a red light that never came do not
137180 // belong to the next one.
138181 if (trigger == RuleTrigger .IGNITION_OFF ) DeferredWrites .clear()
182+ // The onboard set is cleared when the next drive starts, not when this one
183+ // ends: the IGNITION_OFF rules are precisely the ones that want to know
184+ // whose phone made the trip, and they evaluate on a thread of their own.
185+ if (trigger == RuleTrigger .IGNITION_ON ) {
186+ BtOnboard .reset()
187+ startOnboardSampling()
188+ } else {
189+ stopOnboardSampling()
190+ }
139191 if (AppState .isAutomationEnabled(this )) {
140192 AppLogger .i(TAG , " Ignition $state → evaluating ${trigger.name} rules" )
141193 thread(name = " mg4-tasker-cycle" ) { RuleCycle .run (this , trigger.name) }
0 commit comments