@mentra/crust 3.3.0-dev.316 → 3.3.0-dev.320
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/android/build.gradle +2 -0
- package/android/src/main/java/com/mentra/crust/CrustModule.kt +2 -1
- package/android/src/main/java/com/mentra/crust/navigation/NavigationManager.kt +70 -10
- package/android/src/main/java/com/mentra/crust/navigation/NavigationSessionGuard.kt +74 -0
- package/android/src/test/java/com/mentra/crust/navigation/NavigationSessionGuardTest.kt +68 -0
- package/package.json +2 -2
package/android/build.gradle
CHANGED
|
@@ -82,6 +82,8 @@ android {
|
|
|
82
82
|
}
|
|
83
83
|
|
|
84
84
|
dependencies {
|
|
85
|
+
testImplementation 'junit:junit:4.13.2'
|
|
86
|
+
|
|
85
87
|
// Mapbox Navigation SDK v3 (replaces Google Navigation SDK).
|
|
86
88
|
// Drives headless turn-by-turn nav in NavigationManager.kt: route
|
|
87
89
|
// progress, off-route + automatic rerouting, enhanced (map-matched)
|
|
@@ -6,6 +6,7 @@ import com.mentra.crust.services.NotificationListener
|
|
|
6
6
|
import com.mentra.crust.services.NotificationProcessBridge
|
|
7
7
|
import expo.modules.kotlin.modules.Module
|
|
8
8
|
import expo.modules.kotlin.modules.ModuleDefinition
|
|
9
|
+
import expo.modules.kotlin.functions.Queues
|
|
9
10
|
import java.net.URL
|
|
10
11
|
|
|
11
12
|
import com.mentra.crust.navigation.NavigationManager
|
|
@@ -922,7 +923,7 @@ class CrustModule : Module() {
|
|
|
922
923
|
android.util.Log.e("CrustModule", "stopNavigation failed", e)
|
|
923
924
|
mapOf("ok" to false, "error" to (e.message ?: "stop failed"))
|
|
924
925
|
}
|
|
925
|
-
}
|
|
926
|
+
}.runOnQueue(Queues.MAIN)
|
|
926
927
|
|
|
927
928
|
// Dev-only: nudge the simulated position ~offsetMeters off-route to
|
|
928
929
|
// exercise the Nav SDK's onRerouting() pipeline without having to
|
|
@@ -88,6 +88,7 @@ object NavigationManager {
|
|
|
88
88
|
|
|
89
89
|
private var mapboxNavigation: MapboxNavigation? = null
|
|
90
90
|
private var appContext: Context? = null
|
|
91
|
+
private val sessionGuard = NavigationSessionGuard()
|
|
91
92
|
|
|
92
93
|
private var activeCallbacks: Callbacks? = null
|
|
93
94
|
private var lastEmittedKey: String? = null
|
|
@@ -153,6 +154,9 @@ object NavigationManager {
|
|
|
153
154
|
*/
|
|
154
155
|
private var skipCrossingsEnabled: Boolean = false
|
|
155
156
|
private var skipCrossingsTimer: java.util.Timer? = null
|
|
157
|
+
private val simulationHandler = android.os.Handler(android.os.Looper.getMainLooper())
|
|
158
|
+
@Volatile private var deviationRerouteRunnable: Runnable? = null
|
|
159
|
+
@Volatile private var deviationReattachRunnable: Runnable? = null
|
|
156
160
|
private val SKIP_CROSSINGS_BASE_M_PER_TICK = 0.56
|
|
157
161
|
private val SKIP_CROSSINGS_TICK_MS = 400L
|
|
158
162
|
private val CROSSING_MAX_LEG_METERS = 25.0
|
|
@@ -382,6 +386,12 @@ object NavigationManager {
|
|
|
382
386
|
return
|
|
383
387
|
}
|
|
384
388
|
|
|
389
|
+
// Invalidate every callback owned by the previous session before clearing
|
|
390
|
+
// its native state. Mapbox can replay cached observer values on registration
|
|
391
|
+
// and an earlier route request can finish after stop/start.
|
|
392
|
+
val session = sessionGuard.begin()
|
|
393
|
+
cleanupSession(nav)
|
|
394
|
+
|
|
385
395
|
activeCallbacks = callbacks
|
|
386
396
|
activeOptions = options
|
|
387
397
|
simulating = options.simulate
|
|
@@ -389,7 +399,7 @@ object NavigationManager {
|
|
|
389
399
|
offRouteFired = false
|
|
390
400
|
arrivedHandled = false
|
|
391
401
|
|
|
392
|
-
attachObservers(nav, callbacks)
|
|
402
|
+
attachObservers(nav, callbacks, session)
|
|
393
403
|
|
|
394
404
|
// Origin handling — the crux of a correct trip start.
|
|
395
405
|
//
|
|
@@ -407,7 +417,9 @@ object NavigationManager {
|
|
|
407
417
|
// params until the gate fires; `handleLocation` invokes it on the first
|
|
408
418
|
// location and clears it.
|
|
409
419
|
pendingRouteRequest = { originLat, originLng ->
|
|
410
|
-
|
|
420
|
+
if (sessionGuard.acceptsCallback(session)) {
|
|
421
|
+
requestAndStartRoute(nav, activity, options, originLat, originLng, callbacks, session)
|
|
422
|
+
}
|
|
411
423
|
}
|
|
412
424
|
startTripSession(nav, options)
|
|
413
425
|
}
|
|
@@ -424,6 +436,7 @@ object NavigationManager {
|
|
|
424
436
|
originLat: Double,
|
|
425
437
|
originLng: Double,
|
|
426
438
|
callbacks: Callbacks,
|
|
439
|
+
session: NavigationSessionGuard.Token,
|
|
427
440
|
) {
|
|
428
441
|
val coordinates = ArrayList<Point>()
|
|
429
442
|
coordinates.add(Point.fromLngLat(originLng, originLat))
|
|
@@ -446,24 +459,29 @@ object NavigationManager {
|
|
|
446
459
|
routeOptions,
|
|
447
460
|
object : NavigationRouterCallback {
|
|
448
461
|
override fun onRoutesReady(routes: List<NavigationRoute>, routerOrigin: String) {
|
|
462
|
+
if (!sessionGuard.acceptsCallback(session)) return
|
|
449
463
|
if (routes.isEmpty()) {
|
|
450
464
|
callbacks.onError("no route found")
|
|
451
465
|
return
|
|
452
466
|
}
|
|
467
|
+
val route = routes.first()
|
|
468
|
+
if (!sessionGuard.activateInitialRoute(session, route.id)) return
|
|
453
469
|
nav.setNavigationRoutes(routes)
|
|
454
470
|
// In sim mode the ReplayRouteSession observes setNavigationRoutes
|
|
455
471
|
// and starts driving the puck automatically — no manual replay
|
|
456
472
|
// kick needed here.
|
|
457
|
-
emitRoute(
|
|
473
|
+
emitRoute(route, callbacks)
|
|
458
474
|
}
|
|
459
475
|
|
|
460
476
|
override fun onFailure(reasons: List<RouterFailure>, routeOptions: RouteOptions) {
|
|
477
|
+
if (!sessionGuard.acceptsCallback(session)) return
|
|
461
478
|
val msg = reasons.firstOrNull()?.message ?: "route request failed"
|
|
462
479
|
Log.e(TAG, "requestRoutes failed: $msg")
|
|
463
480
|
callbacks.onError(msg)
|
|
464
481
|
}
|
|
465
482
|
|
|
466
483
|
override fun onCanceled(routeOptions: RouteOptions, routerOrigin: String) {
|
|
484
|
+
if (!sessionGuard.acceptsCallback(session)) return
|
|
467
485
|
Log.w(TAG, "route request canceled")
|
|
468
486
|
}
|
|
469
487
|
},
|
|
@@ -588,7 +606,11 @@ object NavigationManager {
|
|
|
588
606
|
|
|
589
607
|
fun stop() {
|
|
590
608
|
Log.d(TAG, "stop")
|
|
591
|
-
|
|
609
|
+
sessionGuard.invalidate()
|
|
610
|
+
cleanupSession(mapboxNavigation)
|
|
611
|
+
}
|
|
612
|
+
|
|
613
|
+
private fun cleanupSession(nav: MapboxNavigation?) {
|
|
592
614
|
activeCallbacks = null
|
|
593
615
|
lastEmittedKey = null
|
|
594
616
|
simulating = false
|
|
@@ -597,6 +619,10 @@ object NavigationManager {
|
|
|
597
619
|
// Tear down dev walkers first.
|
|
598
620
|
skipCrossingsEnabled = false
|
|
599
621
|
skipCrossingsTimer?.cancel(); skipCrossingsTimer = null
|
|
622
|
+
deviationRerouteRunnable?.let(simulationHandler::removeCallbacks)
|
|
623
|
+
deviationRerouteRunnable = null
|
|
624
|
+
deviationReattachRunnable?.let(simulationHandler::removeCallbacks)
|
|
625
|
+
deviationReattachRunnable = null
|
|
600
626
|
wrongSidewalkOffsetEnabled = false
|
|
601
627
|
|
|
602
628
|
if (nav != null) {
|
|
@@ -637,7 +663,11 @@ object NavigationManager {
|
|
|
637
663
|
// Observers — translate Mapbox events into the existing Callbacks shape.
|
|
638
664
|
// =====================================================================
|
|
639
665
|
|
|
640
|
-
private fun attachObservers(
|
|
666
|
+
private fun attachObservers(
|
|
667
|
+
nav: MapboxNavigation,
|
|
668
|
+
callbacks: Callbacks,
|
|
669
|
+
session: NavigationSessionGuard.Token,
|
|
670
|
+
) {
|
|
641
671
|
// REROUTING IS FULLY OWNED BY MAPBOX. Auto-reroute is ON by default in
|
|
642
672
|
// v3 — when the user diverges, the SDK detects off-route AND fetches a
|
|
643
673
|
// new route on its own. We do NOT hand-roll perpendicular-distance
|
|
@@ -653,6 +683,7 @@ object NavigationManager {
|
|
|
653
683
|
// clear our off-route flag and (for the host) reads as a fresh route.
|
|
654
684
|
val routesObs = RoutesObserver { result: RoutesUpdatedResult ->
|
|
655
685
|
val route = result.navigationRoutes.firstOrNull() ?: return@RoutesObserver
|
|
686
|
+
if (!sessionGuard.acceptRouteUpdate(session, route.id)) return@RoutesObserver
|
|
656
687
|
val reason = result.reason
|
|
657
688
|
// Ignore refresh (same geometry, just live traffic) — it would
|
|
658
689
|
// needlessly re-emit the whole polyline + rebuild pivots.
|
|
@@ -669,6 +700,7 @@ object NavigationManager {
|
|
|
669
700
|
// RouteProgressObserver — every location tick. Carries current step,
|
|
670
701
|
// upcoming maneuver, distance-to-maneuver, and trip totals.
|
|
671
702
|
val progressObs = RouteProgressObserver { progress: RouteProgress ->
|
|
703
|
+
if (!sessionGuard.acceptsProgress(session, progress.navigationRoute.id)) return@RouteProgressObserver
|
|
672
704
|
handleRouteProgress(progress, callbacks)
|
|
673
705
|
}
|
|
674
706
|
routeProgressObserver = progressObs
|
|
@@ -679,17 +711,22 @@ object NavigationManager {
|
|
|
679
711
|
val locObs = object : LocationObserver {
|
|
680
712
|
override fun onNewRawLocation(rawLocation: Location) { /* prefer matched */ }
|
|
681
713
|
override fun onNewLocationMatcherResult(result: LocationMatcherResult) {
|
|
714
|
+
if (!sessionGuard.acceptsLocation(session)) return
|
|
682
715
|
handleLocation(result, callbacks)
|
|
683
716
|
}
|
|
684
717
|
}
|
|
685
718
|
locationObserver = locObs
|
|
686
719
|
nav.registerLocationObserver(locObs)
|
|
720
|
+
// Mapbox synchronously replays its cached matcher result from register().
|
|
721
|
+
// Only locations delivered after registration belong to this fresh trip.
|
|
722
|
+
sessionGuard.enableLocationCallbacks(session)
|
|
687
723
|
|
|
688
724
|
// OffRouteObserver — Mapbox's automatic off-route edge signal. PURELY
|
|
689
725
|
// OBSERVATIONAL: registering it does NOT disable auto-reroute. We
|
|
690
726
|
// surface onOffRoute for the UI's "off route" banner; the SDK handles
|
|
691
727
|
// the actual reroute and the new route arrives via RoutesObserver.
|
|
692
728
|
val offRouteObs = OffRouteObserver { offRoute: Boolean ->
|
|
729
|
+
if (!sessionGuard.acceptsCallback(session)) return@OffRouteObserver
|
|
693
730
|
if (offRoute && !offRouteFired) {
|
|
694
731
|
offRouteFired = true
|
|
695
732
|
// Distance from the route is best-effort context for the banner;
|
|
@@ -707,6 +744,7 @@ object NavigationManager {
|
|
|
707
744
|
// route then lands via RoutesObserver(REROUTE). Available only while
|
|
708
745
|
// auto-reroute is enabled (getRerouteController() is null otherwise).
|
|
709
746
|
val rerouteObs = RerouteController.RerouteStateObserver { state: RerouteState ->
|
|
747
|
+
if (!sessionGuard.acceptsCallback(session)) return@RerouteStateObserver
|
|
710
748
|
when (state) {
|
|
711
749
|
is RerouteState.FetchingRoute -> {
|
|
712
750
|
Log.d(TAG, "reroute state: FetchingRoute → onRerouting")
|
|
@@ -1054,6 +1092,10 @@ object NavigationManager {
|
|
|
1054
1092
|
*/
|
|
1055
1093
|
@OptIn(ExperimentalPreviewMapboxNavigationAPI::class)
|
|
1056
1094
|
fun simulateDeviation(offsetMeters: Double = 0.0) {
|
|
1095
|
+
val session = sessionGuard.captureActive() ?: run {
|
|
1096
|
+
Log.w(TAG, "simulateDeviation: no active navigation session")
|
|
1097
|
+
return
|
|
1098
|
+
}
|
|
1057
1099
|
val replayer = replayer ?: run {
|
|
1058
1100
|
Log.w(TAG, "simulateDeviation: not in simulate mode — nothing to push off-route")
|
|
1059
1101
|
return
|
|
@@ -1138,8 +1180,11 @@ object NavigationManager {
|
|
|
1138
1180
|
// onRoute exactly like an automatic reroute. Fire it slightly after
|
|
1139
1181
|
// the off-route events start playing so the controller reroutes from
|
|
1140
1182
|
// the diverged position, not the on-route one.
|
|
1141
|
-
|
|
1142
|
-
|
|
1183
|
+
deviationRerouteRunnable?.let(simulationHandler::removeCallbacks)
|
|
1184
|
+
lateinit var rerouteRunnable: Runnable
|
|
1185
|
+
rerouteRunnable = Runnable {
|
|
1186
|
+
if (deviationRerouteRunnable === rerouteRunnable) deviationRerouteRunnable = null
|
|
1187
|
+
if (!sessionGuard.acceptsCallback(session)) return@Runnable
|
|
1143
1188
|
try {
|
|
1144
1189
|
val controller = mapboxNavigation?.getRerouteController()
|
|
1145
1190
|
if (controller != null) {
|
|
@@ -1148,6 +1193,7 @@ object NavigationManager {
|
|
|
1148
1193
|
Log.d(TAG, "simulateDeviation: requesting reroute() directly")
|
|
1149
1194
|
controller.reroute(
|
|
1150
1195
|
RerouteController.RoutesCallback { routes: List<NavigationRoute>, _: String ->
|
|
1196
|
+
if (!sessionGuard.acceptsCallback(session)) return@RoutesCallback
|
|
1151
1197
|
// RoutesObserver(REROUTE) will also fire and emit the route;
|
|
1152
1198
|
// we don't setNavigationRoutes here (the controller does).
|
|
1153
1199
|
Log.d(TAG, "simulateDeviation: reroute() returned ${routes.size} route(s)")
|
|
@@ -1159,20 +1205,28 @@ object NavigationManager {
|
|
|
1159
1205
|
} catch (e: Throwable) {
|
|
1160
1206
|
Log.e(TAG, "simulateDeviation reroute() failed", e)
|
|
1161
1207
|
}
|
|
1162
|
-
}
|
|
1208
|
+
}
|
|
1209
|
+
deviationRerouteRunnable = rerouteRunnable
|
|
1210
|
+
simulationHandler.postDelayed(rerouteRunnable, 1500L)
|
|
1163
1211
|
|
|
1164
1212
|
// Re-attach the session so it resumes driving the puck once a route is
|
|
1165
1213
|
// active again (locationResetEnabled snaps it onto the new route).
|
|
1166
1214
|
// Delay until AFTER the off-route events + reroute fetch.
|
|
1167
1215
|
val playSeconds = (ts / simulationSpeed.coerceAtLeast(0.1f)).toDouble()
|
|
1168
1216
|
val reattachMs = ((playSeconds + 4.0) * 1000.0).toLong().coerceIn(5000L, 20000L)
|
|
1169
|
-
|
|
1217
|
+
deviationReattachRunnable?.let(simulationHandler::removeCallbacks)
|
|
1218
|
+
lateinit var reattachRunnable: Runnable
|
|
1219
|
+
reattachRunnable = Runnable {
|
|
1220
|
+
if (deviationReattachRunnable === reattachRunnable) deviationReattachRunnable = null
|
|
1221
|
+
if (!sessionGuard.acceptsCallback(session)) return@Runnable
|
|
1170
1222
|
try {
|
|
1171
1223
|
if (simulating) replaySession?.onAttached(nav)
|
|
1172
1224
|
} catch (e: Throwable) {
|
|
1173
1225
|
Log.w(TAG, "simulateDeviation: re-attach session failed: ${e.message}")
|
|
1174
1226
|
}
|
|
1175
|
-
}
|
|
1227
|
+
}
|
|
1228
|
+
deviationReattachRunnable = reattachRunnable
|
|
1229
|
+
simulationHandler.postDelayed(reattachRunnable, reattachMs)
|
|
1176
1230
|
} catch (e: Throwable) {
|
|
1177
1231
|
Log.e(TAG, "simulateDeviation push failed", e)
|
|
1178
1232
|
}
|
|
@@ -1199,6 +1253,10 @@ object NavigationManager {
|
|
|
1199
1253
|
}
|
|
1200
1254
|
|
|
1201
1255
|
private fun startSkipCrossingsWalker() {
|
|
1256
|
+
val session = sessionGuard.captureActive() ?: run {
|
|
1257
|
+
Log.w(TAG, "startSkipCrossingsWalker: no active navigation session")
|
|
1258
|
+
return
|
|
1259
|
+
}
|
|
1202
1260
|
val flat = activePolyline
|
|
1203
1261
|
if (flat == null || flat.size < 2) { Log.w(TAG, "startSkipCrossingsWalker: no route polyline"); return }
|
|
1204
1262
|
try { replayer?.stop() } catch (_: Throwable) {}
|
|
@@ -1215,6 +1273,8 @@ object NavigationManager {
|
|
|
1215
1273
|
timer.scheduleAtFixedRate(object : java.util.TimerTask() {
|
|
1216
1274
|
override fun run() {
|
|
1217
1275
|
mainHandler.post {
|
|
1276
|
+
if (!sessionGuard.acceptsCallback(session)) return@post
|
|
1277
|
+
if (skipCrossingsTimer !== timer) return@post
|
|
1218
1278
|
try {
|
|
1219
1279
|
if (!skipCrossingsEnabled) { cancel(); return@post }
|
|
1220
1280
|
cursor = advanceCursor(modified, cursor, stepMeters)
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
package com.mentra.crust.navigation
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Separates callbacks from successive uses of the process-wide MapboxNavigation instance.
|
|
5
|
+
* Mapbox observers replay cached values when registered, and route requests can complete
|
|
6
|
+
* after stop(), so callers must pass this guard before touching shared navigation state.
|
|
7
|
+
*/
|
|
8
|
+
internal class NavigationSessionGuard {
|
|
9
|
+
@JvmInline
|
|
10
|
+
value class Token internal constructor(internal val value: Long)
|
|
11
|
+
|
|
12
|
+
private var generation = 0L
|
|
13
|
+
private var activeSession = false
|
|
14
|
+
private var activeRouteId: String? = null
|
|
15
|
+
private var locationCallbacksEnabled = false
|
|
16
|
+
|
|
17
|
+
@Synchronized
|
|
18
|
+
fun begin(): Token {
|
|
19
|
+
generation += 1
|
|
20
|
+
activeSession = true
|
|
21
|
+
activeRouteId = null
|
|
22
|
+
locationCallbacksEnabled = false
|
|
23
|
+
return Token(generation)
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
@Synchronized
|
|
27
|
+
fun invalidate() {
|
|
28
|
+
generation += 1
|
|
29
|
+
activeSession = false
|
|
30
|
+
activeRouteId = null
|
|
31
|
+
locationCallbacksEnabled = false
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
@Synchronized
|
|
35
|
+
fun acceptsCallback(token: Token): Boolean = token.value == generation
|
|
36
|
+
|
|
37
|
+
/** Snapshot the active session for work that will execute asynchronously. */
|
|
38
|
+
@Synchronized
|
|
39
|
+
fun captureActive(): Token? = if (activeSession) Token(generation) else null
|
|
40
|
+
|
|
41
|
+
/** Enable locations only after observer registration has replayed its cached values. */
|
|
42
|
+
@Synchronized
|
|
43
|
+
fun enableLocationCallbacks(token: Token) {
|
|
44
|
+
if (token.value == generation) locationCallbacksEnabled = true
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
@Synchronized
|
|
48
|
+
fun acceptsLocation(token: Token): Boolean =
|
|
49
|
+
token.value == generation && locationCallbacksEnabled
|
|
50
|
+
|
|
51
|
+
/** Accept the guarded initial request and establish the route progress may belong to. */
|
|
52
|
+
@Synchronized
|
|
53
|
+
fun activateInitialRoute(token: Token, routeId: String): Boolean {
|
|
54
|
+
if (token.value != generation) return false
|
|
55
|
+
activeRouteId = routeId
|
|
56
|
+
return true
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* Accept a route observer update only after this session established its initial route.
|
|
61
|
+
* A different route id is valid here: Mapbox uses the observer for automatic reroutes.
|
|
62
|
+
*/
|
|
63
|
+
@Synchronized
|
|
64
|
+
fun acceptRouteUpdate(token: Token, routeId: String): Boolean {
|
|
65
|
+
if (token.value != generation || activeRouteId == null) return false
|
|
66
|
+
activeRouteId = routeId
|
|
67
|
+
return true
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/** Reject cached or queued progress from another route, including after a reroute. */
|
|
71
|
+
@Synchronized
|
|
72
|
+
fun acceptsProgress(token: Token, routeId: String): Boolean =
|
|
73
|
+
token.value == generation && routeId == activeRouteId
|
|
74
|
+
}
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
package com.mentra.crust.navigation
|
|
2
|
+
|
|
3
|
+
import org.junit.Assert.assertFalse
|
|
4
|
+
import org.junit.Assert.assertTrue
|
|
5
|
+
import org.junit.Test
|
|
6
|
+
|
|
7
|
+
class NavigationSessionGuardTest {
|
|
8
|
+
@Test
|
|
9
|
+
fun cachedCallbacksAreRejectedUntilTheCurrentInitialRouteIsReady() {
|
|
10
|
+
val guard = NavigationSessionGuard()
|
|
11
|
+
val session = guard.begin()
|
|
12
|
+
|
|
13
|
+
assertTrue(guard.acceptsCallback(session))
|
|
14
|
+
assertFalse(guard.acceptsLocation(session))
|
|
15
|
+
assertFalse(guard.acceptRouteUpdate(session, "cached-route"))
|
|
16
|
+
assertFalse(guard.acceptsProgress(session, "cached-route"))
|
|
17
|
+
|
|
18
|
+
assertTrue(guard.activateInitialRoute(session, "current-route"))
|
|
19
|
+
assertTrue(guard.acceptsProgress(session, "current-route"))
|
|
20
|
+
|
|
21
|
+
guard.enableLocationCallbacks(session)
|
|
22
|
+
assertTrue(guard.acceptsLocation(session))
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
@Test
|
|
26
|
+
fun stoppedAndSupersededSessionsCannotMutateCurrentState() {
|
|
27
|
+
val guard = NavigationSessionGuard()
|
|
28
|
+
val oldSession = guard.begin()
|
|
29
|
+
assertTrue(guard.activateInitialRoute(oldSession, "old-route"))
|
|
30
|
+
|
|
31
|
+
guard.invalidate()
|
|
32
|
+
assertFalse(guard.acceptsCallback(oldSession))
|
|
33
|
+
guard.enableLocationCallbacks(oldSession)
|
|
34
|
+
assertFalse(guard.acceptsLocation(oldSession))
|
|
35
|
+
assertFalse(guard.activateInitialRoute(oldSession, "late-route"))
|
|
36
|
+
assertFalse(guard.acceptRouteUpdate(oldSession, "late-reroute"))
|
|
37
|
+
assertFalse(guard.acceptsProgress(oldSession, "old-route"))
|
|
38
|
+
|
|
39
|
+
val currentSession = guard.begin()
|
|
40
|
+
assertTrue(guard.activateInitialRoute(currentSession, "current-route"))
|
|
41
|
+
assertTrue(guard.acceptsProgress(currentSession, "current-route"))
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
@Test
|
|
45
|
+
fun rerouteMovesProgressIdentityToTheNewRoute() {
|
|
46
|
+
val guard = NavigationSessionGuard()
|
|
47
|
+
val session = guard.begin()
|
|
48
|
+
assertTrue(guard.activateInitialRoute(session, "initial-route"))
|
|
49
|
+
|
|
50
|
+
assertTrue(guard.acceptRouteUpdate(session, "rerouted-route"))
|
|
51
|
+
assertFalse(guard.acceptsProgress(session, "initial-route"))
|
|
52
|
+
assertTrue(guard.acceptsProgress(session, "rerouted-route"))
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
@Test
|
|
56
|
+
fun delayedWorkCapturedBeforeStopCannotRunInTheNextSession() {
|
|
57
|
+
val guard = NavigationSessionGuard()
|
|
58
|
+
val oldWork = guard.begin()
|
|
59
|
+
assertTrue(guard.captureActive() == oldWork)
|
|
60
|
+
|
|
61
|
+
guard.invalidate()
|
|
62
|
+
assertTrue(guard.captureActive() == null)
|
|
63
|
+
val nextSession = guard.begin()
|
|
64
|
+
|
|
65
|
+
assertFalse(guard.acceptsCallback(oldWork))
|
|
66
|
+
assertTrue(guard.acceptsCallback(nextSession))
|
|
67
|
+
}
|
|
68
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@mentra/crust",
|
|
3
|
-
"version": "3.3.0-dev.
|
|
3
|
+
"version": "3.3.0-dev.320",
|
|
4
4
|
"description": "Mentra Native Module",
|
|
5
5
|
"main": "build/index.js",
|
|
6
6
|
"types": "build/index.d.ts",
|
|
@@ -35,7 +35,7 @@
|
|
|
35
35
|
"license": "Apache-2.0",
|
|
36
36
|
"homepage": "https://github.com/Mentra-Community/MentraOS/tree/dev/mobile/modules/crust#readme",
|
|
37
37
|
"dependencies": {
|
|
38
|
-
"@mentra/jspolyfill": "3.3.0-dev.
|
|
38
|
+
"@mentra/jspolyfill": "3.3.0-dev.320"
|
|
39
39
|
},
|
|
40
40
|
"devDependencies": {
|
|
41
41
|
"@expo/config-plugins": ">=8.0.0",
|