@mentra/acs-meeting 3.2.0-dev.222 → 3.2.0-dev.225

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.
Files changed (106) hide show
  1. package/README.md +14 -3
  2. package/android/build.gradle +1 -0
  3. package/android/src/main/java/com/mentra/acsmeeting/AcsMeetingModule.kt +10 -10
  4. package/android/src/main/java/com/mentra/acsmeeting/AcsMeetingSession.kt +37 -37
  5. package/android/src/main/java/com/mentra/acsmeeting/audio/UplinkSender.kt +4 -4
  6. package/android/src/main/java/com/mentra/acsmeeting/source/MeetingVideoSourceSpec.kt +3 -0
  7. package/android/src/main/java/com/mentra/acsmeeting/video/AcsFrameSender.kt +12 -10
  8. package/android/src/main/java/com/mentra/acsmeeting/video/I420FormatSpec.kt +2 -0
  9. package/android/src/main/java/com/mentra/acsmeeting/video/Nv12Packer.kt +2 -0
  10. package/android/src/test/java/com/mentra/acsmeeting/source/{AcsInvestigationTest.kt → MediaDiagnosticsTest.kt} +10 -10
  11. package/android/src/test/java/com/mentra/acsmeeting/source/MeetingVideoSourceSpecTest.kt +3 -0
  12. package/android/src/test/java/com/mentra/acsmeeting/telemetry/VideoQualityTest.kt +2 -2
  13. package/android/src/test/java/com/mentra/acsmeeting/video/I420FormatSpecTest.kt +2 -0
  14. package/android/src/test/java/com/mentra/acsmeeting/video/VideoProfileTest.kt +2 -2
  15. package/build/AcsMeeting.types.d.ts +10 -1
  16. package/build/AcsMeeting.types.d.ts.map +1 -1
  17. package/build/AcsMeeting.types.js.map +1 -1
  18. package/build/AcsMeetingModule.d.ts +2 -2
  19. package/build/AcsMeetingModule.d.ts.map +1 -1
  20. package/build/AcsMeetingModule.js.map +1 -1
  21. package/ios/AcsMeeting.podspec +1 -1
  22. package/ios/AcsMeetingModule.swift +284 -148
  23. package/ios/PolicyKit/Package.swift +2 -1
  24. package/ios/PolicyKit/Sources/AcsAudioPolicy/CallbackOperation.swift +60 -0
  25. package/ios/PolicyKit/Sources/AcsAudioPolicy/CapturePolicy.swift +5 -0
  26. package/ios/PolicyKit/Tests/AcsAudioPolicyTests/AcsAudioPolicyTests.swift +1 -0
  27. package/ios/PolicyKit/Tests/AcsAudioPolicyTests/CallAgentRetirementTests.swift +113 -0
  28. package/package.json +3 -2
  29. package/src/AcsMeeting.types.ts +4 -1
  30. package/src/AcsMeetingModule.ts +2 -2
  31. package/android/src/androidTest/java/com/mentra/acsmeeting/network/SoftApFeasibilityGateTest.kt +0 -291
  32. package/android/src/main/java/com/mentra/acsmeeting/network/InternetHold.kt +0 -501
  33. package/android/src/main/java/com/mentra/acsmeeting/network/Ipv4Prefix.kt +0 -51
  34. package/android/src/main/java/com/mentra/acsmeeting/network/NetworkFacts.kt +0 -81
  35. package/android/src/main/java/com/mentra/acsmeeting/network/ScopedNetworkChangeDetector.kt +0 -355
  36. package/android/src/main/java/com/mentra/acsmeeting/network/ScopedNetworkError.kt +0 -109
  37. package/android/src/main/java/com/mentra/acsmeeting/network/ScopedNetworkObserver.kt +0 -160
  38. package/android/src/main/java/com/mentra/acsmeeting/network/ScopedNetworkReadiness.kt +0 -103
  39. package/android/src/main/java/com/mentra/acsmeeting/network/ScopedNetworkRequestSpec.kt +0 -53
  40. package/android/src/main/java/com/mentra/acsmeeting/network/ScopedNetworkState.kt +0 -127
  41. package/android/src/main/java/com/mentra/acsmeeting/network/ScopedSoftApNetwork.kt +0 -573
  42. package/android/src/main/java/com/mentra/acsmeeting/source/BitmapFont.kt +0 -99
  43. package/android/src/main/java/com/mentra/acsmeeting/source/CloudflareWhepSource.kt +0 -487
  44. package/android/src/main/java/com/mentra/acsmeeting/source/DecodedTrackRelay.kt +0 -147
  45. package/android/src/main/java/com/mentra/acsmeeting/source/FirstFrameGate.kt +0 -45
  46. package/android/src/main/java/com/mentra/acsmeeting/source/GlassesMediaSource.kt +0 -143
  47. package/android/src/main/java/com/mentra/acsmeeting/source/LocalWhipIngestSource.kt +0 -772
  48. package/android/src/main/java/com/mentra/acsmeeting/source/MediaListeners.kt +0 -57
  49. package/android/src/main/java/com/mentra/acsmeeting/source/SdpAdapter.kt +0 -17
  50. package/android/src/main/java/com/mentra/acsmeeting/source/SelectedIcePair.kt +0 -139
  51. package/android/src/main/java/com/mentra/acsmeeting/source/SoftApIceDiagnosis.kt +0 -144
  52. package/android/src/main/java/com/mentra/acsmeeting/source/SoftApIcePolicy.kt +0 -46
  53. package/android/src/main/java/com/mentra/acsmeeting/source/SoftApSdpGuard.kt +0 -160
  54. package/android/src/main/java/com/mentra/acsmeeting/source/SyntheticFrameFactory.kt +0 -205
  55. package/android/src/main/java/com/mentra/acsmeeting/source/SyntheticI420Source.kt +0 -214
  56. package/android/src/main/java/com/mentra/acsmeeting/source/TrackRegistry.kt +0 -33
  57. package/android/src/main/java/com/mentra/acsmeeting/source/VideoSourceArm.kt +0 -110
  58. package/android/src/main/java/com/mentra/acsmeeting/source/WhipIngestProtocol.kt +0 -207
  59. package/android/src/main/java/com/mentra/acsmeeting/source/WhipIngestServer.kt +0 -356
  60. package/android/src/main/java/com/mentra/acsmeeting/telemetry/AvSyncProbe.kt +0 -155
  61. package/android/src/main/java/com/mentra/acsmeeting/telemetry/ChromaProbe.kt +0 -78
  62. package/android/src/main/java/com/mentra/acsmeeting/telemetry/PipelineStats.kt +0 -372
  63. package/android/src/main/java/com/mentra/acsmeeting/telemetry/PipelineTicker.kt +0 -79
  64. package/android/src/main/java/com/mentra/acsmeeting/telemetry/ProcessCpu.kt +0 -15
  65. package/android/src/main/java/com/mentra/acsmeeting/telemetry/RingPercentile.kt +0 -48
  66. package/android/src/main/java/com/mentra/acsmeeting/telemetry/VideoQuality.kt +0 -178
  67. package/android/src/main/java/com/mentra/acsmeeting/telemetry/VideoRateVerdict.kt +0 -145
  68. package/android/src/main/java/com/mentra/acsmeeting/trace/SoftApTrace.kt +0 -143
  69. package/android/src/main/java/com/mentra/acsmeeting/video/FrameGeometry.kt +0 -22
  70. package/android/src/main/java/com/mentra/acsmeeting/video/I420Packer.kt +0 -72
  71. package/android/src/test/java/com/mentra/acsmeeting/network/InternetHoldLifecycleTest.kt +0 -102
  72. package/android/src/test/java/com/mentra/acsmeeting/network/InternetHoldTest.kt +0 -148
  73. package/android/src/test/java/com/mentra/acsmeeting/network/Ipv4PrefixTest.kt +0 -59
  74. package/android/src/test/java/com/mentra/acsmeeting/network/NetworkFactsTest.kt +0 -59
  75. package/android/src/test/java/com/mentra/acsmeeting/network/ScopedLocalNetworkPermissionTest.kt +0 -35
  76. package/android/src/test/java/com/mentra/acsmeeting/network/ScopedNetworkChangeDetectorTest.kt +0 -165
  77. package/android/src/test/java/com/mentra/acsmeeting/network/ScopedNetworkErrorTest.kt +0 -96
  78. package/android/src/test/java/com/mentra/acsmeeting/network/ScopedNetworkLifecycleTest.kt +0 -187
  79. package/android/src/test/java/com/mentra/acsmeeting/network/ScopedNetworkObserverTest.kt +0 -172
  80. package/android/src/test/java/com/mentra/acsmeeting/network/ScopedNetworkReadinessTest.kt +0 -141
  81. package/android/src/test/java/com/mentra/acsmeeting/network/ScopedNetworkRequestSpecTest.kt +0 -69
  82. package/android/src/test/java/com/mentra/acsmeeting/source/BitmapFontTest.kt +0 -22
  83. package/android/src/test/java/com/mentra/acsmeeting/source/DecodedTrackRelayTest.kt +0 -41
  84. package/android/src/test/java/com/mentra/acsmeeting/source/FirstFrameGateTest.kt +0 -60
  85. package/android/src/test/java/com/mentra/acsmeeting/source/GlassesMediaSourceTest.kt +0 -151
  86. package/android/src/test/java/com/mentra/acsmeeting/source/I420PlanesTest.kt +0 -67
  87. package/android/src/test/java/com/mentra/acsmeeting/source/SelectedIcePairTest.kt +0 -165
  88. package/android/src/test/java/com/mentra/acsmeeting/source/SoftApIceDiagnosisTest.kt +0 -148
  89. package/android/src/test/java/com/mentra/acsmeeting/source/SoftApIcePolicyTest.kt +0 -76
  90. package/android/src/test/java/com/mentra/acsmeeting/source/SoftApSdpGuardTest.kt +0 -275
  91. package/android/src/test/java/com/mentra/acsmeeting/source/SyntheticFrameFactoryTest.kt +0 -193
  92. package/android/src/test/java/com/mentra/acsmeeting/source/SyntheticI420SourceTest.kt +0 -176
  93. package/android/src/test/java/com/mentra/acsmeeting/source/TrackRegistryTest.kt +0 -45
  94. package/android/src/test/java/com/mentra/acsmeeting/source/WhipIngestProtocolTest.kt +0 -356
  95. package/android/src/test/java/com/mentra/acsmeeting/source/WhipIngestServerLoopbackTest.kt +0 -411
  96. package/android/src/test/java/com/mentra/acsmeeting/telemetry/AvSyncProbeTest.kt +0 -83
  97. package/android/src/test/java/com/mentra/acsmeeting/telemetry/ChromaProbeTest.kt +0 -61
  98. package/android/src/test/java/com/mentra/acsmeeting/telemetry/PipelineStatsTest.kt +0 -322
  99. package/android/src/test/java/com/mentra/acsmeeting/telemetry/ProcessCpuTest.kt +0 -18
  100. package/android/src/test/java/com/mentra/acsmeeting/telemetry/RingPercentileTest.kt +0 -49
  101. package/android/src/test/java/com/mentra/acsmeeting/telemetry/VideoRateVerdictTest.kt +0 -149
  102. package/android/src/test/java/com/mentra/acsmeeting/trace/SoftApTraceTest.kt +0 -97
  103. package/android/src/test/java/com/mentra/acsmeeting/video/FrameGeometryTest.kt +0 -33
  104. package/android/src/test/java/com/mentra/acsmeeting/video/I420PackerTest.kt +0 -126
  105. package/ios/PolicyKit/Sources/AcsAudioPolicy/GlassesMediaSource.swift +0 -68
  106. package/ios/WhepVideoSource.swift +0 -356
@@ -1,187 +0,0 @@
1
- package com.mentra.acsmeeting.network
2
-
3
- import com.mentra.acsmeeting.network.ScopedNetworkState.Failure
4
- import com.mentra.acsmeeting.network.ScopedNetworkState.Phase
5
- import org.assertj.core.api.Assertions.assertThat
6
- import org.junit.Test
7
-
8
- /**
9
- * ConnectivityManager gives no ordering or at-most-once guarantee, so these cover the awkward
10
- * orderings that would otherwise only show up on a device: duplicate callbacks, a callback from a
11
- * request that was already replaced, and a timeout racing a late onAvailable.
12
- */
13
- class ScopedNetworkLifecycleTest {
14
-
15
- @Test
16
- fun `starts idle`() {
17
- val state = ScopedNetworkState()
18
- assertThat(state.phase).isEqualTo(Phase.IDLE)
19
- assertThat(state.failure).isEqualTo(Failure.NONE)
20
- assertThat(state.isActive).isFalse()
21
- }
22
-
23
- @Test
24
- fun `happy path is idle to requesting to available`() {
25
- val state = ScopedNetworkState()
26
- val generation = state.startRequest()
27
- assertThat(state.phase).isEqualTo(Phase.REQUESTING)
28
-
29
- assertThat(state.onAvailable(generation)).isTrue()
30
- assertThat(state.phase).isEqualTo(Phase.AVAILABLE)
31
- assertThat(state.failure).isEqualTo(Failure.NONE)
32
- }
33
-
34
- @Test
35
- fun `a duplicate onAvailable does not re-transition`() {
36
- val state = ScopedNetworkState()
37
- val generation = state.startRequest()
38
-
39
- assertThat(state.onAvailable(generation)).isTrue()
40
- assertThat(state.onAvailable(generation)).isFalse()
41
- assertThat(state.phase).isEqualTo(Phase.AVAILABLE)
42
- }
43
-
44
- @Test
45
- fun `losing an available network reports LOST`() {
46
- val state = ScopedNetworkState()
47
- val generation = state.startRequest()
48
- state.onAvailable(generation)
49
-
50
- assertThat(state.onLost(generation)).isTrue()
51
- assertThat(state.phase).isEqualTo(Phase.LOST)
52
- assertThat(state.failure).isEqualTo(Failure.LOST)
53
- }
54
-
55
- @Test
56
- fun `a second onLost is a no-op`() {
57
- val state = ScopedNetworkState()
58
- val generation = state.startRequest()
59
- state.onAvailable(generation)
60
- state.onLost(generation)
61
-
62
- assertThat(state.onLost(generation)).isFalse()
63
- }
64
-
65
- @Test
66
- fun `timeout while still requesting fails as TIMEOUT`() {
67
- val state = ScopedNetworkState()
68
- val generation = state.startRequest()
69
-
70
- assertThat(state.onTimeout(generation)).isTrue()
71
- assertThat(state.phase).isEqualTo(Phase.LOST)
72
- assertThat(state.failure).isEqualTo(Failure.TIMEOUT)
73
- }
74
-
75
- /** The join succeeded just before the watchdog fired; the network must stay usable. */
76
- @Test
77
- fun `a timeout arriving after onAvailable is ignored`() {
78
- val state = ScopedNetworkState()
79
- val generation = state.startRequest()
80
- state.onAvailable(generation)
81
-
82
- assertThat(state.onTimeout(generation)).isFalse()
83
- assertThat(state.phase).isEqualTo(Phase.AVAILABLE)
84
- assertThat(state.failure).isEqualTo(Failure.NONE)
85
- }
86
-
87
- @Test
88
- fun `onUnavailable fails as UNAVAILABLE`() {
89
- val state = ScopedNetworkState()
90
- val generation = state.startRequest()
91
-
92
- assertThat(state.onUnavailable(generation)).isTrue()
93
- assertThat(state.failure).isEqualTo(Failure.UNAVAILABLE)
94
- }
95
-
96
- @Test
97
- fun `a SecurityException maps to PERMISSION_DENIED`() {
98
- val state = ScopedNetworkState()
99
- val generation = state.startRequest()
100
-
101
- assertThat(state.onRequestFailed(generation, permissionDenied = true)).isTrue()
102
- assertThat(state.failure).isEqualTo(Failure.PERMISSION_DENIED)
103
- }
104
-
105
- @Test
106
- fun `any other request throw maps to REQUEST_FAILED`() {
107
- val state = ScopedNetworkState()
108
- val generation = state.startRequest()
109
-
110
- assertThat(state.onRequestFailed(generation, permissionDenied = false)).isTrue()
111
- assertThat(state.failure).isEqualTo(Failure.REQUEST_FAILED)
112
- }
113
-
114
- // -----------------------------------------------------------------
115
- // Stale generations
116
- // -----------------------------------------------------------------
117
-
118
- @Test
119
- fun `callbacks from a superseded request are rejected`() {
120
- val state = ScopedNetworkState()
121
- val first = state.startRequest()
122
- val second = state.startRequest()
123
- assertThat(second).isNotEqualTo(first)
124
-
125
- assertThat(state.onAvailable(first)).isFalse()
126
- assertThat(state.onLost(first)).isFalse()
127
- assertThat(state.onUnavailable(first)).isFalse()
128
- assertThat(state.onTimeout(first)).isFalse()
129
- assertThat(state.phase).isEqualTo(Phase.REQUESTING)
130
-
131
- assertThat(state.onAvailable(second)).isTrue()
132
- }
133
-
134
- @Test
135
- fun `a late onLost after release does not resurrect state`() {
136
- val state = ScopedNetworkState()
137
- val generation = state.startRequest()
138
- state.onAvailable(generation)
139
- state.release()
140
-
141
- assertThat(state.onLost(generation)).isFalse()
142
- assertThat(state.failure).isEqualTo(Failure.NONE)
143
- }
144
-
145
- @Test
146
- fun `release is idempotent`() {
147
- val state = ScopedNetworkState()
148
- val generation = state.startRequest()
149
- state.onAvailable(generation)
150
-
151
- assertThat(state.release()).isTrue()
152
- assertThat(state.release()).isFalse()
153
- }
154
-
155
- @Test
156
- fun `release from idle is a no-op`() {
157
- assertThat(ScopedNetworkState().release()).isFalse()
158
- }
159
-
160
- @Test
161
- fun `reset returns the machine to idle for reuse`() {
162
- val state = ScopedNetworkState()
163
- val generation = state.startRequest()
164
- state.onLost(generation)
165
- state.reset()
166
-
167
- assertThat(state.phase).isEqualTo(Phase.IDLE)
168
- assertThat(state.failure).isEqualTo(Failure.NONE)
169
-
170
- val next = state.startRequest()
171
- assertThat(state.onAvailable(next)).isTrue()
172
- }
173
-
174
- /** Ten start/stop cycles must leave no residue, matching the rejoin soak requirement. */
175
- @Test
176
- fun `ten join and release cycles end clean`() {
177
- val state = ScopedNetworkState()
178
- repeat(10) {
179
- val generation = state.startRequest()
180
- assertThat(state.onAvailable(generation)).isTrue()
181
- assertThat(state.release()).isTrue()
182
- state.reset()
183
- assertThat(state.phase).isEqualTo(Phase.IDLE)
184
- }
185
- assertThat(state.generation).isEqualTo(10)
186
- }
187
- }
@@ -1,172 +0,0 @@
1
- package com.mentra.acsmeeting.network
2
-
3
- import com.mentra.acsmeeting.network.ScopedNetworkChangeDetector.ScopedInterface
4
- import org.assertj.core.api.Assertions.assertThat
5
- import org.junit.Test
6
- import org.webrtc.NetworkChangeDetector
7
-
8
- /**
9
- * The takeover keeps a process-wide monitor from answering SoftAP with a leftover cellular handle;
10
- * the restore keeps that takeover from breaking the next WHEP or LiveKit connection in the same
11
- * process. Both halves are tested against a recording observer rather than the on-device gate.
12
- */
13
- class ScopedNetworkObserverTest {
14
-
15
- private val connected = mutableListOf<Long>()
16
- private val disconnected = mutableListOf<Long>()
17
- private var scoped: ScopedInterface? = softAp()
18
-
19
- private val downstream =
20
- object : NetworkChangeDetector.Observer() {
21
- override fun onConnectionTypeChanged(connectionType: NetworkChangeDetector.ConnectionType) = Unit
22
-
23
- override fun onNetworkConnect(networkInfo: NetworkChangeDetector.NetworkInformation) {
24
- connected.add(networkInfo.handle)
25
- }
26
-
27
- override fun onNetworkDisconnect(networkHandle: Long) {
28
- disconnected.add(networkHandle)
29
- }
30
-
31
- override fun onNetworkPreference(
32
- types: MutableList<NetworkChangeDetector.ConnectionType>,
33
- preference: Int,
34
- ) = Unit
35
- }
36
-
37
- private val observer = ScopedNetworkObserver(downstream) { scoped }
38
-
39
- private fun ipv4(a: Int, b: Int, c: Int, d: Int) =
40
- byteArrayOf(a.toByte(), b.toByte(), c.toByte(), d.toByte())
41
-
42
- private fun softAp() = ScopedInterface("wlan0", 99L, listOf(ipv4(192, 168, 43, 79)))
43
-
44
- private fun hotspot() = ScopedNetworkChangeDetector.toNetworkInformation(softAp())!!
45
-
46
- private fun info(name: String, handle: Long, address: ByteArray = ipv4(10, 1, 2, 3)) =
47
- NetworkChangeDetector.NetworkInformation(
48
- name,
49
- NetworkChangeDetector.ConnectionType.CONNECTION_4G,
50
- NetworkChangeDetector.ConnectionType.CONNECTION_NONE,
51
- handle,
52
- arrayOf(NetworkChangeDetector.IPAddress(address)),
53
- )
54
-
55
- @Test
56
- fun `a stock connect is swallowed while SoftAP is joined`() {
57
- observer.onNetworkConnect(info("rmnet_data0", 7L))
58
-
59
- assertThat(connected).isEmpty()
60
- }
61
-
62
- /**
63
- * The monitor is already running when a SoftAP call starts: ACS joins over cellular first, so
64
- * those handles are in the native list before ingest exists. They have to be dropped.
65
- */
66
- @Test
67
- fun `publish drops networks announced before the join and connects only the inventory`() {
68
- observer.onNetworkConnect(info("rmnet_data0", 7L))
69
- observer.onNetworkConnect(info("wlan0", 8L, ipv4(192, 168, 50, 7)))
70
-
71
- observer.publish(listOf(hotspot()))
72
-
73
- assertThat(disconnected).containsExactly(7L, 8L)
74
- assertThat(connected).containsExactly(99L)
75
- }
76
-
77
- @Test
78
- fun `a second publish of the same inventory is a no-op`() {
79
- observer.publish(listOf(hotspot()))
80
- connected.clear()
81
-
82
- observer.publish(listOf(hotspot()))
83
-
84
- assertThat(connected).isEmpty()
85
- assertThat(disconnected).isEmpty()
86
- }
87
-
88
- /**
89
- * Teardown followed by ordinary WebRTC. Resuming pass-through alone would leave these networks
90
- * missing until the OS happened to re-announce them, and leave the synthetic entry in place.
91
- */
92
- @Test
93
- fun `teardown drops the synthetic entry and replays the suppressed networks`() {
94
- observer.onNetworkConnect(info("rmnet_data0", 7L))
95
- observer.publish(listOf(hotspot()))
96
- connected.clear()
97
- disconnected.clear()
98
-
99
- scoped = null
100
- observer.restore()
101
-
102
- assertThat(disconnected).containsExactly(99L)
103
- assertThat(connected).containsExactly(7L)
104
- }
105
-
106
- /** A network that genuinely went away while SoftAP was up must not come back in the replay. */
107
- @Test
108
- fun `a network lost during the call is not replayed on teardown`() {
109
- observer.onNetworkConnect(info("rmnet_data0", 7L))
110
- observer.onNetworkConnect(info("wlan0", 8L, ipv4(192, 168, 50, 7)))
111
- observer.publish(listOf(hotspot()))
112
- observer.onNetworkDisconnect(8L)
113
- connected.clear()
114
-
115
- scoped = null
116
- observer.restore()
117
-
118
- assertThat(connected).containsExactly(7L)
119
- }
120
-
121
- @Test
122
- fun `restore is idempotent and a no-op when ownership was never taken`() {
123
- scoped = null
124
-
125
- observer.restore()
126
- observer.restore()
127
-
128
- assertThat(connected).isEmpty()
129
- assertThat(disconnected).isEmpty()
130
- }
131
-
132
- /** Restoration is also driven from the OS side, since after teardown it may be the only caller. */
133
- @Test
134
- fun `a stock callback after teardown restores before forwarding, and forwards once`() {
135
- observer.onNetworkConnect(info("rmnet_data0", 7L))
136
- observer.publish(listOf(hotspot()))
137
- connected.clear()
138
- disconnected.clear()
139
-
140
- scoped = null
141
- observer.onNetworkConnect(info("wlan0", 8L, ipv4(192, 168, 50, 7)))
142
-
143
- assertThat(disconnected).containsExactly(99L)
144
- assertThat(connected).containsExactly(7L, 8L)
145
- }
146
-
147
- @Test
148
- fun `stock callbacks pass through once the hotspot is gone`() {
149
- scoped = null
150
-
151
- observer.onNetworkConnect(info("rmnet_data0", 7L))
152
-
153
- assertThat(connected).containsExactly(7L)
154
- }
155
-
156
- /** Rejoin is part of the acceptance bar, so the takeover has to re-arm after a restore. */
157
- @Test
158
- fun `an immediate rejoin takes the monitor back over`() {
159
- observer.onNetworkConnect(info("rmnet_data0", 7L))
160
- observer.publish(listOf(hotspot()))
161
- scoped = null
162
- observer.restore()
163
- connected.clear()
164
- disconnected.clear()
165
-
166
- scoped = softAp()
167
- observer.publish(listOf(hotspot()))
168
-
169
- assertThat(disconnected).containsExactly(7L)
170
- assertThat(connected).containsExactly(99L)
171
- }
172
- }
@@ -1,141 +0,0 @@
1
- package com.mentra.acsmeeting.network
2
-
3
- import com.mentra.acsmeeting.network.ScopedNetworkReadiness.Verdict
4
- import org.assertj.core.api.Assertions.assertThat
5
- import org.junit.Test
6
-
7
- /**
8
- * The race these cases pin cost a misdiagnosis: ingest started on `onAvailable`, the hotspot address
9
- * was not yet in the kernel table, and the resulting candidate-less answer was blamed on socket
10
- * binding. Readiness now has to agree from three sources, and the missing one has to be named.
11
- */
12
- class ScopedNetworkReadinessTest {
13
-
14
- private val readiness = ScopedNetworkReadiness()
15
-
16
- private fun joined() {
17
- readiness.onAvailable()
18
- readiness.onCapabilities()
19
- readiness.onLinkProperties("wlan0", "192.168.43.79")
20
- }
21
-
22
- @Test
23
- fun `a fresh gate waits for onAvailable`() {
24
- assertThat(readiness.verdict()).isEqualTo(Verdict.Waiting("onAvailable"))
25
- assertThat(readiness.callbacksSatisfied()).isFalse
26
- }
27
-
28
- /** Android's guidance, and the reason `onAvailable` alone was not enough. */
29
- @Test
30
- fun `onAvailable alone is not ready`() {
31
- readiness.onAvailable()
32
-
33
- assertThat(readiness.verdict()).isEqualTo(Verdict.Waiting("onCapabilitiesChanged"))
34
- }
35
-
36
- @Test
37
- fun `capabilities without an address is not ready`() {
38
- readiness.onAvailable()
39
- readiness.onCapabilities()
40
-
41
- assertThat(readiness.verdict()).isEqualTo(Verdict.Waiting("an IPv4 address from onLinkPropertiesChanged"))
42
- }
43
-
44
- /** The whole point: the callbacks can all agree while ICE still has nothing to gather. */
45
- @Test
46
- fun `every callback reporting is not ready until the kernel table has the address`() {
47
- joined()
48
-
49
- assertThat(readiness.callbacksSatisfied()).isTrue
50
- assertThat(readiness.verdict())
51
- .isEqualTo(Verdict.Waiting("the kernel interface table to carry 192.168.43.79"))
52
- }
53
-
54
- @Test
55
- fun `the address appearing in the table late completes readiness`() {
56
- joined()
57
-
58
- readiness.onInterfaceTable("wlan0")
59
-
60
- assertThat(readiness.verdict()).isEqualTo(Verdict.Ready("192.168.43.79", "wlan0", "wlan0"))
61
- }
62
-
63
- /**
64
- * A name disagreement is reported, not blocked on: ICE gathers per address, so the address being
65
- * in the table is what matters. Blocking here would fail calls that actually work.
66
- */
67
- @Test
68
- fun `the kernel owning the address under another name is still ready`() {
69
- joined()
70
-
71
- readiness.onInterfaceTable("wlan1")
72
-
73
- val verdict = readiness.verdict() as Verdict.Ready
74
- assertThat(verdict.interfaceName).isEqualTo("wlan0")
75
- assertThat(verdict.owner).isEqualTo("wlan1")
76
- }
77
-
78
- /** A later poll that no longer finds the address must un-ready the gate, not latch. */
79
- @Test
80
- fun `the address leaving the table takes readiness away again`() {
81
- joined()
82
- readiness.onInterfaceTable("wlan0")
83
-
84
- readiness.onInterfaceTable(null)
85
-
86
- assertThat(readiness.verdict()).isInstanceOf(Verdict.Waiting::class.java)
87
- }
88
-
89
- /** LinkProperties is a whole-state snapshot, so a later one without an address means gone. */
90
- @Test
91
- fun `a link properties update without an address clears the observation`() {
92
- joined()
93
- readiness.onInterfaceTable("wlan0")
94
-
95
- readiness.onLinkProperties("wlan0", null)
96
-
97
- assertThat(readiness.callbacksSatisfied()).isFalse
98
- }
99
-
100
- /** The synchronous read inside `onAvailable` is a hint; it must not erase a real callback. */
101
- @Test
102
- fun `seeding with no address does not clear what a callback reported`() {
103
- readiness.onLinkProperties("wlan0", "192.168.43.79")
104
-
105
- readiness.seedLinkProperties(null, null)
106
-
107
- assertThat(readiness.observations.linkAddress).isEqualTo("192.168.43.79")
108
- }
109
-
110
- @Test
111
- fun `seeding supplies the address when the callback has not arrived`() {
112
- readiness.onAvailable()
113
- readiness.seedLinkProperties("wlan0", "192.168.43.79")
114
- readiness.onCapabilities()
115
-
116
- assertThat(readiness.callbacksSatisfied()).isTrue
117
- }
118
-
119
- /** Rejoin reuses the instance, so a stale Ready from the previous call would be fatal. */
120
- @Test
121
- fun `reset returns the gate to waiting for onAvailable`() {
122
- joined()
123
- readiness.onInterfaceTable("wlan0")
124
-
125
- readiness.reset()
126
-
127
- assertThat(readiness.verdict()).isEqualTo(Verdict.Waiting("onAvailable"))
128
- }
129
-
130
- @Test
131
- fun `callbacks arriving out of order still resolve`() {
132
- readiness.onCapabilities()
133
- readiness.onLinkProperties("wlan0", "192.168.43.79")
134
- readiness.onInterfaceTable("wlan0")
135
- assertThat(readiness.verdict()).isEqualTo(Verdict.Waiting("onAvailable"))
136
-
137
- readiness.onAvailable()
138
-
139
- assertThat(readiness.verdict()).isInstanceOf(Verdict.Ready::class.java)
140
- }
141
- }
@@ -1,69 +0,0 @@
1
- package com.mentra.acsmeeting.network
2
-
3
- import android.net.NetworkCapabilities
4
- import org.assertj.core.api.Assertions.assertThat
5
- import org.junit.Test
6
-
7
- /**
8
- * Guards the one property the whole dual-homing design rests on: the SoftAP request must not ask
9
- * for `NET_CAPABILITY_INTERNET`. If that regresses, Android can promote the glasses hotspot to the
10
- * default network and ACS traffic silently stops using cellular.
11
- */
12
- class ScopedNetworkRequestSpecTest {
13
-
14
- @Test
15
- fun `the softap request removes the internet capability`() {
16
- val spec = ScopedNetworkRequestSpec.forSoftAp("MentraLive-1234", "hunter2")
17
-
18
- assertThat(spec.removedCapabilities).contains(NetworkCapabilities.NET_CAPABILITY_INTERNET)
19
- assertThat(spec.avoidsInternetCapability).isTrue()
20
- }
21
-
22
- @Test
23
- fun `the softap request is wifi only`() {
24
- val spec = ScopedNetworkRequestSpec.forSoftAp("MentraLive-1234", "hunter2")
25
-
26
- assertThat(spec.transportTypes).containsExactly(NetworkCapabilities.TRANSPORT_WIFI)
27
- }
28
-
29
- @Test
30
- fun `ssid and passphrase are carried through`() {
31
- val spec = ScopedNetworkRequestSpec.forSoftAp("MentraLive-1234", "hunter2")
32
-
33
- assertThat(spec.ssid).isEqualTo("MentraLive-1234")
34
- assertThat(spec.passphrase).isEqualTo("hunter2")
35
- assertThat(spec.hasPassphrase).isTrue()
36
- }
37
-
38
- @Test
39
- fun `an open network reports no passphrase`() {
40
- assertThat(ScopedNetworkRequestSpec.forSoftAp("Open-AP", "").hasPassphrase).isFalse()
41
- }
42
-
43
- @Test
44
- fun `the default timeout matches the bluetooth sdk hotspot join`() {
45
- assertThat(ScopedNetworkRequestSpec.forSoftAp("x", "y").timeoutMs).isEqualTo(30_000)
46
- assertThat(ScopedNetworkRequestSpec.DEFAULT_TIMEOUT_MS).isEqualTo(30_000)
47
- }
48
-
49
- @Test
50
- fun `the timeout is overridable`() {
51
- assertThat(ScopedNetworkRequestSpec.forSoftAp("x", "y", timeoutMs = 5_000).timeoutMs)
52
- .isEqualTo(5_000)
53
- }
54
-
55
- /** A spec that kept the internet capability must not read as safe. */
56
- @Test
57
- fun `avoidsInternetCapability is false when the capability is not removed`() {
58
- val unsafe =
59
- ScopedNetworkRequestSpec(
60
- ssid = "x",
61
- passphrase = "y",
62
- transportTypes = setOf(NetworkCapabilities.TRANSPORT_WIFI),
63
- removedCapabilities = emptySet(),
64
- timeoutMs = 1_000,
65
- )
66
-
67
- assertThat(unsafe.avoidsInternetCapability).isFalse()
68
- }
69
- }
@@ -1,22 +0,0 @@
1
- package com.mentra.acsmeeting.source
2
-
3
- import org.assertj.core.api.Assertions.assertThat
4
- import org.junit.Test
5
- import java.nio.ByteBuffer
6
-
7
- class BitmapFontTest {
8
- @Test
9
- fun drawsOnPixelsForDigitEight() {
10
- val width = 80
11
- val height = 40
12
- val dest = ByteBuffer.allocate(width * height)
13
- BitmapFont.draw(dest, width, height, "8", 0, 0, 2, 0xEB.toByte())
14
- var on = 0
15
- for (i in 0 until dest.capacity()) {
16
- if (dest.get(i) == 0xEB.toByte()) on += 1
17
- }
18
- assertThat(on).isGreaterThan(0)
19
- assertThat(BitmapFont.textWidth(1, 2)).isEqualTo(5 * 2)
20
- assertThat(BitmapFont.textHeight(2)).isEqualTo(7 * 2)
21
- }
22
- }
@@ -1,41 +0,0 @@
1
- package com.mentra.acsmeeting.source
2
-
3
- import org.assertj.core.api.Assertions.assertThat
4
- import org.junit.Test
5
-
6
- /**
7
- * Covers the scale decision the relay makes on every frame.
8
- *
9
- * This code was previously inline in [CloudflareWhepSource] with no coverage at all, and it is
10
- * decided per frame on the decode thread. Both directions are invisible when wrong: an unnecessary
11
- * scale is a silent per-frame CPU cost, and a skipped one is a plane-size mismatch at the ACS
12
- * sender rather than an exception here.
13
- */
14
- class DecodedTrackRelayTest {
15
-
16
- @Test
17
- fun `no target size means ACS has not negotiated yet, so the buffer passes through`() {
18
- assertThat(DecodedTrackRelay.needsScale(null, 1280, 720)).isFalse()
19
- }
20
-
21
- @Test
22
- fun `a target matching the buffer needs no scale`() {
23
- assertThat(DecodedTrackRelay.needsScale(TargetSize(1280, 720), 1280, 720)).isFalse()
24
- }
25
-
26
- @Test
27
- fun `a differing width or height needs a scale`() {
28
- assertThat(DecodedTrackRelay.needsScale(TargetSize(640, 720), 1280, 720)).isTrue()
29
- assertThat(DecodedTrackRelay.needsScale(TargetSize(1280, 360), 1280, 720)).isTrue()
30
- assertThat(DecodedTrackRelay.needsScale(TargetSize(640, 360), 1280, 720)).isTrue()
31
- }
32
-
33
- /**
34
- * A transposed target is a real size change, not a rotation to be ignored. The relay works in
35
- * unrotated buffer coordinates, so 720x1280 against a 1280x720 buffer must scale.
36
- */
37
- @Test
38
- fun `a transposed target still needs a scale`() {
39
- assertThat(DecodedTrackRelay.needsScale(TargetSize(720, 1280), 1280, 720)).isTrue()
40
- }
41
- }
@@ -1,60 +0,0 @@
1
- package com.mentra.acsmeeting.source
2
-
3
- import org.assertj.core.api.Assertions.assertThat
4
- import org.junit.Test
5
-
6
- class FirstFrameGateTest {
7
- @Test
8
- fun answerAloneDoesNotPromote() {
9
- val gate = FirstFrameGate()
10
- gate.arm(1)
11
- // The answer is armed, not live: nothing has reached the sink yet.
12
- assertThat(gate.expired(1)).isTrue()
13
- }
14
-
15
- @Test
16
- fun firstFrameForTheArmedGenerationPromotesExactlyOnce() {
17
- val gate = FirstFrameGate()
18
- gate.arm(7)
19
- assertThat(gate.onFrame(7)).isTrue()
20
- assertThat(gate.onFrame(7)).isFalse()
21
- assertThat(gate.expired(7)).isFalse()
22
- }
23
-
24
- @Test
25
- fun framesFromAPeerBeingTornDownNeverPromote() {
26
- val gate = FirstFrameGate()
27
- gate.arm(2)
28
- assertThat(gate.onFrame(1)).isFalse()
29
- assertThat(gate.expired(2)).isTrue()
30
- }
31
-
32
- @Test
33
- fun deadlineForASupersededGenerationDoesNotFailTheNewPeer() {
34
- val gate = FirstFrameGate()
35
- gate.arm(3)
36
- gate.arm(4)
37
- assertThat(gate.expired(3)).isFalse()
38
- assertThat(gate.expired(4)).isTrue()
39
- }
40
-
41
- @Test
42
- fun iceRecoveryRearmsSoTheNextFramePromotesAgain() {
43
- val gate = FirstFrameGate()
44
- gate.arm(5)
45
- assertThat(gate.onFrame(5)).isTrue()
46
- // ICE bounced and came back on the same peer: LIVE has to be re-earned.
47
- gate.arm(5)
48
- assertThat(gate.expired(5)).isTrue()
49
- assertThat(gate.onFrame(5)).isTrue()
50
- }
51
-
52
- @Test
53
- fun resetClearsTheArmSoAStoppedSourceCannotBeFailedLater() {
54
- val gate = FirstFrameGate()
55
- gate.arm(6)
56
- gate.reset()
57
- assertThat(gate.expired(6)).isFalse()
58
- assertThat(gate.onFrame(6)).isFalse()
59
- }
60
- }