@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.
- package/README.md +14 -3
- package/android/build.gradle +1 -0
- package/android/src/main/java/com/mentra/acsmeeting/AcsMeetingModule.kt +10 -10
- package/android/src/main/java/com/mentra/acsmeeting/AcsMeetingSession.kt +37 -37
- package/android/src/main/java/com/mentra/acsmeeting/audio/UplinkSender.kt +4 -4
- package/android/src/main/java/com/mentra/acsmeeting/source/MeetingVideoSourceSpec.kt +3 -0
- package/android/src/main/java/com/mentra/acsmeeting/video/AcsFrameSender.kt +12 -10
- package/android/src/main/java/com/mentra/acsmeeting/video/I420FormatSpec.kt +2 -0
- package/android/src/main/java/com/mentra/acsmeeting/video/Nv12Packer.kt +2 -0
- package/android/src/test/java/com/mentra/acsmeeting/source/{AcsInvestigationTest.kt → MediaDiagnosticsTest.kt} +10 -10
- package/android/src/test/java/com/mentra/acsmeeting/source/MeetingVideoSourceSpecTest.kt +3 -0
- package/android/src/test/java/com/mentra/acsmeeting/telemetry/VideoQualityTest.kt +2 -2
- package/android/src/test/java/com/mentra/acsmeeting/video/I420FormatSpecTest.kt +2 -0
- package/android/src/test/java/com/mentra/acsmeeting/video/VideoProfileTest.kt +2 -2
- package/build/AcsMeeting.types.d.ts +10 -1
- package/build/AcsMeeting.types.d.ts.map +1 -1
- package/build/AcsMeeting.types.js.map +1 -1
- package/build/AcsMeetingModule.d.ts +2 -2
- package/build/AcsMeetingModule.d.ts.map +1 -1
- package/build/AcsMeetingModule.js.map +1 -1
- package/ios/AcsMeeting.podspec +1 -1
- package/ios/AcsMeetingModule.swift +284 -148
- package/ios/PolicyKit/Package.swift +2 -1
- package/ios/PolicyKit/Sources/AcsAudioPolicy/CallbackOperation.swift +60 -0
- package/ios/PolicyKit/Sources/AcsAudioPolicy/CapturePolicy.swift +5 -0
- package/ios/PolicyKit/Tests/AcsAudioPolicyTests/AcsAudioPolicyTests.swift +1 -0
- package/ios/PolicyKit/Tests/AcsAudioPolicyTests/CallAgentRetirementTests.swift +113 -0
- package/package.json +3 -2
- package/src/AcsMeeting.types.ts +4 -1
- package/src/AcsMeetingModule.ts +2 -2
- package/android/src/androidTest/java/com/mentra/acsmeeting/network/SoftApFeasibilityGateTest.kt +0 -291
- package/android/src/main/java/com/mentra/acsmeeting/network/InternetHold.kt +0 -501
- package/android/src/main/java/com/mentra/acsmeeting/network/Ipv4Prefix.kt +0 -51
- package/android/src/main/java/com/mentra/acsmeeting/network/NetworkFacts.kt +0 -81
- package/android/src/main/java/com/mentra/acsmeeting/network/ScopedNetworkChangeDetector.kt +0 -355
- package/android/src/main/java/com/mentra/acsmeeting/network/ScopedNetworkError.kt +0 -109
- package/android/src/main/java/com/mentra/acsmeeting/network/ScopedNetworkObserver.kt +0 -160
- package/android/src/main/java/com/mentra/acsmeeting/network/ScopedNetworkReadiness.kt +0 -103
- package/android/src/main/java/com/mentra/acsmeeting/network/ScopedNetworkRequestSpec.kt +0 -53
- package/android/src/main/java/com/mentra/acsmeeting/network/ScopedNetworkState.kt +0 -127
- package/android/src/main/java/com/mentra/acsmeeting/network/ScopedSoftApNetwork.kt +0 -573
- package/android/src/main/java/com/mentra/acsmeeting/source/BitmapFont.kt +0 -99
- package/android/src/main/java/com/mentra/acsmeeting/source/CloudflareWhepSource.kt +0 -487
- package/android/src/main/java/com/mentra/acsmeeting/source/DecodedTrackRelay.kt +0 -147
- package/android/src/main/java/com/mentra/acsmeeting/source/FirstFrameGate.kt +0 -45
- package/android/src/main/java/com/mentra/acsmeeting/source/GlassesMediaSource.kt +0 -143
- package/android/src/main/java/com/mentra/acsmeeting/source/LocalWhipIngestSource.kt +0 -772
- package/android/src/main/java/com/mentra/acsmeeting/source/MediaListeners.kt +0 -57
- package/android/src/main/java/com/mentra/acsmeeting/source/SdpAdapter.kt +0 -17
- package/android/src/main/java/com/mentra/acsmeeting/source/SelectedIcePair.kt +0 -139
- package/android/src/main/java/com/mentra/acsmeeting/source/SoftApIceDiagnosis.kt +0 -144
- package/android/src/main/java/com/mentra/acsmeeting/source/SoftApIcePolicy.kt +0 -46
- package/android/src/main/java/com/mentra/acsmeeting/source/SoftApSdpGuard.kt +0 -160
- package/android/src/main/java/com/mentra/acsmeeting/source/SyntheticFrameFactory.kt +0 -205
- package/android/src/main/java/com/mentra/acsmeeting/source/SyntheticI420Source.kt +0 -214
- package/android/src/main/java/com/mentra/acsmeeting/source/TrackRegistry.kt +0 -33
- package/android/src/main/java/com/mentra/acsmeeting/source/VideoSourceArm.kt +0 -110
- package/android/src/main/java/com/mentra/acsmeeting/source/WhipIngestProtocol.kt +0 -207
- package/android/src/main/java/com/mentra/acsmeeting/source/WhipIngestServer.kt +0 -356
- package/android/src/main/java/com/mentra/acsmeeting/telemetry/AvSyncProbe.kt +0 -155
- package/android/src/main/java/com/mentra/acsmeeting/telemetry/ChromaProbe.kt +0 -78
- package/android/src/main/java/com/mentra/acsmeeting/telemetry/PipelineStats.kt +0 -372
- package/android/src/main/java/com/mentra/acsmeeting/telemetry/PipelineTicker.kt +0 -79
- package/android/src/main/java/com/mentra/acsmeeting/telemetry/ProcessCpu.kt +0 -15
- package/android/src/main/java/com/mentra/acsmeeting/telemetry/RingPercentile.kt +0 -48
- package/android/src/main/java/com/mentra/acsmeeting/telemetry/VideoQuality.kt +0 -178
- package/android/src/main/java/com/mentra/acsmeeting/telemetry/VideoRateVerdict.kt +0 -145
- package/android/src/main/java/com/mentra/acsmeeting/trace/SoftApTrace.kt +0 -143
- package/android/src/main/java/com/mentra/acsmeeting/video/FrameGeometry.kt +0 -22
- package/android/src/main/java/com/mentra/acsmeeting/video/I420Packer.kt +0 -72
- package/android/src/test/java/com/mentra/acsmeeting/network/InternetHoldLifecycleTest.kt +0 -102
- package/android/src/test/java/com/mentra/acsmeeting/network/InternetHoldTest.kt +0 -148
- package/android/src/test/java/com/mentra/acsmeeting/network/Ipv4PrefixTest.kt +0 -59
- package/android/src/test/java/com/mentra/acsmeeting/network/NetworkFactsTest.kt +0 -59
- package/android/src/test/java/com/mentra/acsmeeting/network/ScopedLocalNetworkPermissionTest.kt +0 -35
- package/android/src/test/java/com/mentra/acsmeeting/network/ScopedNetworkChangeDetectorTest.kt +0 -165
- package/android/src/test/java/com/mentra/acsmeeting/network/ScopedNetworkErrorTest.kt +0 -96
- package/android/src/test/java/com/mentra/acsmeeting/network/ScopedNetworkLifecycleTest.kt +0 -187
- package/android/src/test/java/com/mentra/acsmeeting/network/ScopedNetworkObserverTest.kt +0 -172
- package/android/src/test/java/com/mentra/acsmeeting/network/ScopedNetworkReadinessTest.kt +0 -141
- package/android/src/test/java/com/mentra/acsmeeting/network/ScopedNetworkRequestSpecTest.kt +0 -69
- package/android/src/test/java/com/mentra/acsmeeting/source/BitmapFontTest.kt +0 -22
- package/android/src/test/java/com/mentra/acsmeeting/source/DecodedTrackRelayTest.kt +0 -41
- package/android/src/test/java/com/mentra/acsmeeting/source/FirstFrameGateTest.kt +0 -60
- package/android/src/test/java/com/mentra/acsmeeting/source/GlassesMediaSourceTest.kt +0 -151
- package/android/src/test/java/com/mentra/acsmeeting/source/I420PlanesTest.kt +0 -67
- package/android/src/test/java/com/mentra/acsmeeting/source/SelectedIcePairTest.kt +0 -165
- package/android/src/test/java/com/mentra/acsmeeting/source/SoftApIceDiagnosisTest.kt +0 -148
- package/android/src/test/java/com/mentra/acsmeeting/source/SoftApIcePolicyTest.kt +0 -76
- package/android/src/test/java/com/mentra/acsmeeting/source/SoftApSdpGuardTest.kt +0 -275
- package/android/src/test/java/com/mentra/acsmeeting/source/SyntheticFrameFactoryTest.kt +0 -193
- package/android/src/test/java/com/mentra/acsmeeting/source/SyntheticI420SourceTest.kt +0 -176
- package/android/src/test/java/com/mentra/acsmeeting/source/TrackRegistryTest.kt +0 -45
- package/android/src/test/java/com/mentra/acsmeeting/source/WhipIngestProtocolTest.kt +0 -356
- package/android/src/test/java/com/mentra/acsmeeting/source/WhipIngestServerLoopbackTest.kt +0 -411
- package/android/src/test/java/com/mentra/acsmeeting/telemetry/AvSyncProbeTest.kt +0 -83
- package/android/src/test/java/com/mentra/acsmeeting/telemetry/ChromaProbeTest.kt +0 -61
- package/android/src/test/java/com/mentra/acsmeeting/telemetry/PipelineStatsTest.kt +0 -322
- package/android/src/test/java/com/mentra/acsmeeting/telemetry/ProcessCpuTest.kt +0 -18
- package/android/src/test/java/com/mentra/acsmeeting/telemetry/RingPercentileTest.kt +0 -49
- package/android/src/test/java/com/mentra/acsmeeting/telemetry/VideoRateVerdictTest.kt +0 -149
- package/android/src/test/java/com/mentra/acsmeeting/trace/SoftApTraceTest.kt +0 -97
- package/android/src/test/java/com/mentra/acsmeeting/video/FrameGeometryTest.kt +0 -33
- package/android/src/test/java/com/mentra/acsmeeting/video/I420PackerTest.kt +0 -126
- package/ios/PolicyKit/Sources/AcsAudioPolicy/GlassesMediaSource.swift +0 -68
- package/ios/WhepVideoSource.swift +0 -356
|
@@ -1,355 +0,0 @@
|
|
|
1
|
-
package com.mentra.acsmeeting.network
|
|
2
|
-
|
|
3
|
-
import android.content.Context
|
|
4
|
-
import android.net.ConnectivityManager
|
|
5
|
-
import com.mentra.acsmeeting.trace.SoftApTrace
|
|
6
|
-
import java.net.Inet4Address
|
|
7
|
-
import org.webrtc.NetworkChangeDetector
|
|
8
|
-
import org.webrtc.NetworkMonitor
|
|
9
|
-
import org.webrtc.NetworkMonitorAutoDetect
|
|
10
|
-
|
|
11
|
-
/**
|
|
12
|
-
* Adds the scoped SoftAP network to libwebrtc's Android network inventory.
|
|
13
|
-
*
|
|
14
|
-
* ## Why this exists
|
|
15
|
-
*
|
|
16
|
-
* The SoftAP [Network] is requested without `NET_CAPABILITY_INTERNET` so that Android keeps ACS on
|
|
17
|
-
* cellular. But stock libwebrtc's `NetworkMonitorAutoDetect` registers a request for
|
|
18
|
-
* *internet-capable* networks, so it never sees this one, and ICE therefore never gathers a host
|
|
19
|
-
* candidate on the hotspot subnet. Holding a scoped `Network` object is not by itself enough to make
|
|
20
|
-
* libwebrtc's internally-created UDP sockets use that interface — `PeerConnectionFactory.Options`
|
|
21
|
-
* exposes `networkIgnoreMask` and `disableNetworkMonitor`, not a socket factory or a network handle.
|
|
22
|
-
*
|
|
23
|
-
* Injecting the interface into the inventory is the supported way through: it is the same technique
|
|
24
|
-
* the glasses already use for their `ap0` tethering interface in
|
|
25
|
-
* `HotspotAwareNetworkChangeDetector`.
|
|
26
|
-
*
|
|
27
|
-
* The monitor this touches is process-wide and the libwebrtc AAR is shared with LiveKit, so the
|
|
28
|
-
* takeover is scoped and reversible; [ScopedNetworkObserver] documents and enforces that contract.
|
|
29
|
-
*
|
|
30
|
-
* ## Why the handle is the real [Network] handle
|
|
31
|
-
*
|
|
32
|
-
* Two device-proven behaviours, both captured, both fatal on their own:
|
|
33
|
-
*
|
|
34
|
-
* Handle `0` makes `BindSocketToNetwork` return `NOT_IMPLEMENTED`, so libwebrtc binds the socket to
|
|
35
|
-
* `192.168.43.x` and advertises that address. The answer is truthful. ICE checks then sit on that
|
|
36
|
-
* pair with `|CR-I|-|0|0|` forever: the OS accepted the bind, and even a send-only datagram, but
|
|
37
|
-
* unmarked UDP on a non-default network is not delivered. 18:56 / 19:01 runs: ACS `CONNECTED`,
|
|
38
|
-
* `ingest_host_candidate onHotspot=true`, then `ingest_no_first_frame` and the glasses reporting
|
|
39
|
-
* `ICE did not connect`.
|
|
40
|
-
*
|
|
41
|
-
* A real handle makes `android_setsocknetwork` succeed, so the socket is marked onto the scoped
|
|
42
|
-
* SoftAP and ICE packets can actually leave and return. libwebrtc then rebinds to `0.0.0.0` and
|
|
43
|
-
* `UDPPort` substitutes the default-route (cellular) address into the candidate:
|
|
44
|
-
*
|
|
45
|
-
* ```
|
|
46
|
-
* Allocate ports on wlan0
|
|
47
|
-
* Port[...:Net[wlan0:192.168.43.x/24:Wifi:id=5]]: Gathered candidate: Cand[...:10.48.51.x:...:host...]
|
|
48
|
-
* ```
|
|
49
|
-
*
|
|
50
|
-
* That advertised address is what the glasses would send to, and they have no route to it. So the
|
|
51
|
-
* handle is real — sockets must be marked — and [SoftApSdpGuard.pinHostAddresses] rewrites the
|
|
52
|
-
* answer the glasses see back onto the scoped IP. One inventory entry, so the handle cannot collide
|
|
53
|
-
* with a decoy the way `0` did when we published more than one.
|
|
54
|
-
*
|
|
55
|
-
* ## Status
|
|
56
|
-
*
|
|
57
|
-
* Whether stock libwebrtc needs this at all is exactly what the Phase 0.5 feasibility gate
|
|
58
|
-
* (`SoftApFeasibilityGateTest`) measures. The gate fired on-device (see [install]), so
|
|
59
|
-
* [AcsMeetingModule][com.mentra.acsmeeting.AcsMeetingModule] installs it at module creation.
|
|
60
|
-
*/
|
|
61
|
-
class ScopedNetworkChangeDetector
|
|
62
|
-
internal constructor(
|
|
63
|
-
private val delegate: NetworkChangeDetector,
|
|
64
|
-
private val scopedNetworkSupplier: () -> ScopedInterface?,
|
|
65
|
-
private val observer: ScopedNetworkObserver? = null,
|
|
66
|
-
) : NetworkChangeDetector {
|
|
67
|
-
|
|
68
|
-
constructor(
|
|
69
|
-
observer: NetworkChangeDetector.Observer,
|
|
70
|
-
context: Context,
|
|
71
|
-
scopedNetwork: ScopedSoftApNetwork,
|
|
72
|
-
) : this(wire(observer, context) { resolveScopedInterface(context, scopedNetwork) })
|
|
73
|
-
|
|
74
|
-
private constructor(wired: Wired) : this(wired.delegate, wired.scoped, wired.observer)
|
|
75
|
-
|
|
76
|
-
/** The SoftAP interface as libwebrtc needs to see it. */
|
|
77
|
-
data class ScopedInterface(
|
|
78
|
-
val name: String,
|
|
79
|
-
val networkHandle: Long,
|
|
80
|
-
val ipv4Addresses: List<ByteArray>,
|
|
81
|
-
)
|
|
82
|
-
|
|
83
|
-
/**
|
|
84
|
-
* One inventory entry as libwebrtc was told about it.
|
|
85
|
-
*
|
|
86
|
-
* Kept as plain values rather than the webrtc type so [lastPublished] can outlive a gathering
|
|
87
|
-
* pass without pinning native objects, and so the diagnosis can be unit tested.
|
|
88
|
-
*/
|
|
89
|
-
data class PublishedEntry(
|
|
90
|
-
val name: String,
|
|
91
|
-
val type: String,
|
|
92
|
-
val handle: Long,
|
|
93
|
-
val addresses: List<String>,
|
|
94
|
-
) {
|
|
95
|
-
override fun toString(): String = "$name[$type]#$handle(${addresses.joinToString("/")})"
|
|
96
|
-
}
|
|
97
|
-
|
|
98
|
-
override fun getCurrentConnectionType(): NetworkChangeDetector.ConnectionType {
|
|
99
|
-
val delegateType = delegate.currentConnectionType
|
|
100
|
-
if (delegateType == NetworkChangeDetector.ConnectionType.CONNECTION_NONE &&
|
|
101
|
-
scopedNetworkSupplier() != null
|
|
102
|
-
) {
|
|
103
|
-
return NetworkChangeDetector.ConnectionType.CONNECTION_WIFI
|
|
104
|
-
}
|
|
105
|
-
return delegateType
|
|
106
|
-
}
|
|
107
|
-
|
|
108
|
-
override fun supportNetworkCallback(): Boolean = delegate.supportNetworkCallback()
|
|
109
|
-
|
|
110
|
-
override fun getActiveNetworkList(): List<NetworkChangeDetector.NetworkInformation> {
|
|
111
|
-
val scoped = scopedNetworkSupplier()
|
|
112
|
-
val table = NetworkFacts.snapshot()
|
|
113
|
-
val stock = delegate.activeNetworkList
|
|
114
|
-
|
|
115
|
-
// The three views that have to agree, logged side by side. libwebrtc gathers from the
|
|
116
|
-
// kernel table; we only get to label the names in it. A scoped address missing from
|
|
117
|
-
// `ifaddrs`, or present under a different name than LinkProperties claims, is
|
|
118
|
-
// unrecoverable by any mask — and is invisible unless all three are printed together.
|
|
119
|
-
SoftApTrace.stage(
|
|
120
|
-
"softap_ifaddrs",
|
|
121
|
-
"gatherable" to NetworkFacts.render(NetworkFacts.gatherable(table)),
|
|
122
|
-
"all" to NetworkFacts.render(table),
|
|
123
|
-
)
|
|
124
|
-
SoftApTrace.stage(
|
|
125
|
-
"webrtc_stock_inventory",
|
|
126
|
-
"count" to stock.orEmpty().size,
|
|
127
|
-
"interfaces" to renderInventory(stock),
|
|
128
|
-
)
|
|
129
|
-
if (scoped != null) {
|
|
130
|
-
val address = scoped.ipv4Addresses.firstOrNull()?.let { formatIpv4(it) }
|
|
131
|
-
val owner = address?.let { NetworkFacts.ownerOf(it, table) }
|
|
132
|
-
// The smoking-gun check. `claimed` is what LinkProperties told us; `owner` is what the
|
|
133
|
-
// kernel says. If owner is null the address is not in the table at all and ICE cannot
|
|
134
|
-
// gather it; if they differ, our WIFI label is being applied to the wrong interface.
|
|
135
|
-
SoftApTrace.stage(
|
|
136
|
-
"softap_scoped_address_owner",
|
|
137
|
-
"address" to (address ?: "none"),
|
|
138
|
-
"claimed" to scoped.name,
|
|
139
|
-
"owner" to (owner ?: "ABSENT"),
|
|
140
|
-
"agrees" to (owner != null && owner == scoped.name),
|
|
141
|
-
"handle" to scoped.networkHandle,
|
|
142
|
-
)
|
|
143
|
-
}
|
|
144
|
-
|
|
145
|
-
val merged = mergeScopedNetwork(stock, scoped)
|
|
146
|
-
lastPublished = snapshotOf(merged)
|
|
147
|
-
SoftApTrace.stage(
|
|
148
|
-
"webrtc_network_inventory",
|
|
149
|
-
"count" to merged.size,
|
|
150
|
-
"interfaces" to lastPublished.joinToString(",") { it.toString() },
|
|
151
|
-
)
|
|
152
|
-
observer?.publish(merged)
|
|
153
|
-
return merged
|
|
154
|
-
}
|
|
155
|
-
|
|
156
|
-
override fun destroy() {
|
|
157
|
-
// Give the monitor back before the delegate goes away, otherwise the synthetic entry is
|
|
158
|
-
// the last thing libwebrtc was told about and every later connection inherits it.
|
|
159
|
-
observer?.restore()
|
|
160
|
-
delegate.destroy()
|
|
161
|
-
}
|
|
162
|
-
|
|
163
|
-
companion object {
|
|
164
|
-
|
|
165
|
-
/**
|
|
166
|
-
* What was last handed to libwebrtc, for the rejection diagnostics.
|
|
167
|
-
*
|
|
168
|
-
* A missing hotspot candidate has several causes and they are only separable by knowing
|
|
169
|
-
* whether the hotspot was in the inventory at gathering time, and whether any two entries
|
|
170
|
-
* shared a handle. Process-wide because the inventory is; read-only for callers.
|
|
171
|
-
*/
|
|
172
|
-
@Volatile
|
|
173
|
-
@JvmStatic
|
|
174
|
-
var lastPublished: List<PublishedEntry> = emptyList()
|
|
175
|
-
private set
|
|
176
|
-
|
|
177
|
-
/** An inventory entry reduced to the fields a diagnosis or a trace needs. */
|
|
178
|
-
@JvmStatic
|
|
179
|
-
fun snapshotOf(
|
|
180
|
-
entries: List<NetworkChangeDetector.NetworkInformation>?,
|
|
181
|
-
): List<PublishedEntry> =
|
|
182
|
-
entries.orEmpty().map { info ->
|
|
183
|
-
PublishedEntry(
|
|
184
|
-
info.name,
|
|
185
|
-
info.type.name,
|
|
186
|
-
info.handle,
|
|
187
|
-
info.ipAddresses.map { formatIpv4(it.address) },
|
|
188
|
-
)
|
|
189
|
-
}
|
|
190
|
-
|
|
191
|
-
/** `name[type]#handle(ips)` per entry — the shape both traces and diagnostics want. */
|
|
192
|
-
@JvmStatic
|
|
193
|
-
fun renderInventory(
|
|
194
|
-
entries: List<NetworkChangeDetector.NetworkInformation>?,
|
|
195
|
-
): String = snapshotOf(entries).joinToString(",") { it.toString() }
|
|
196
|
-
|
|
197
|
-
/**
|
|
198
|
-
* The handle that tells libwebrtc "bind to the address, not to a network".
|
|
199
|
-
*
|
|
200
|
-
* `AndroidNetworkMonitor::BindSocketToNetwork` maps a zero handle to `NOT_IMPLEMENTED`,
|
|
201
|
-
* which is the only branch that leaves the socket bound to a real local address — and so
|
|
202
|
-
* the only branch that yields a truthful host candidate. Named because it is load-bearing,
|
|
203
|
-
* not a placeholder.
|
|
204
|
-
*/
|
|
205
|
-
const val UNBINDABLE_HANDLE = 0L
|
|
206
|
-
|
|
207
|
-
/**
|
|
208
|
-
* Register this detector as the one libwebrtc builds when it starts monitoring.
|
|
209
|
-
*
|
|
210
|
-
* `NetworkMonitor` is process-wide and only consults the factory when it creates a
|
|
211
|
-
* detector — on the first `startMonitoring` and again after every `stopMonitoring` that
|
|
212
|
-
* drops the observer count to zero. So this must run before any `PeerConnectionFactory`
|
|
213
|
-
* exists, and [scopedNetworkSupplier] must resolve the *current* join at call time rather
|
|
214
|
-
* than capture one instance: the module recreates its [ScopedSoftApNetwork] across
|
|
215
|
-
* sessions.
|
|
216
|
-
*
|
|
217
|
-
* Idempotent. Feasibility gate result that made this necessary, on a Galaxy S25 /
|
|
218
|
-
* Android 16 with webrtc-sdk 137: `NetworkMonitorAutoDetect.networkToInfo` drops every
|
|
219
|
-
* network lacking `NET_CAPABILITY_INTERNET`, so the answer to the glasses' offer carried
|
|
220
|
-
* zero host candidates and the ingest returned WHIP 500 `no_softap_host_candidate`.
|
|
221
|
-
*/
|
|
222
|
-
private data class Wired(
|
|
223
|
-
val delegate: NetworkChangeDetector,
|
|
224
|
-
val scoped: () -> ScopedInterface?,
|
|
225
|
-
val observer: ScopedNetworkObserver,
|
|
226
|
-
)
|
|
227
|
-
|
|
228
|
-
/**
|
|
229
|
-
* One observer is shared: AutoDetect's callbacks go through it, and so does the
|
|
230
|
-
* reconcile in [getActiveNetworkList]. Two instances would drop stock handles on one
|
|
231
|
-
* and publish the hotspot on the other, which is how the native list stayed dirty.
|
|
232
|
-
*/
|
|
233
|
-
private fun wire(
|
|
234
|
-
downstream: NetworkChangeDetector.Observer,
|
|
235
|
-
context: Context,
|
|
236
|
-
scoped: () -> ScopedInterface?,
|
|
237
|
-
): Wired {
|
|
238
|
-
val filter = ScopedNetworkObserver(downstream, scoped)
|
|
239
|
-
return Wired(NetworkMonitorAutoDetect(filter, context), scoped, filter)
|
|
240
|
-
}
|
|
241
|
-
|
|
242
|
-
@JvmStatic
|
|
243
|
-
fun install(scopedNetworkSupplier: () -> ScopedSoftApNetwork?) {
|
|
244
|
-
val monitor = NetworkMonitor.getInstance()
|
|
245
|
-
// A live detector was built by an earlier factory (or the stock one) and will not be
|
|
246
|
-
// replaced until monitoring restarts; say so in the trace so a missing host candidate
|
|
247
|
-
// after this point has an explanation.
|
|
248
|
-
SoftApTrace.stage(
|
|
249
|
-
"webrtc_network_detector_installed",
|
|
250
|
-
"monitorObservers" to monitor.numObservers,
|
|
251
|
-
)
|
|
252
|
-
monitor.setNetworkChangeDetectorFactory { observer, context ->
|
|
253
|
-
val wired =
|
|
254
|
-
wire(observer, context) {
|
|
255
|
-
scopedNetworkSupplier()?.let { resolveScopedInterface(context, it) }
|
|
256
|
-
}
|
|
257
|
-
ScopedNetworkChangeDetector(wired.delegate, wired.scoped, wired.observer)
|
|
258
|
-
}
|
|
259
|
-
}
|
|
260
|
-
|
|
261
|
-
/**
|
|
262
|
-
* The SoftAP inventory: exactly one entry, the scoped network, typed Wi-Fi.
|
|
263
|
-
*
|
|
264
|
-
* Publishing decoy entries for the other interfaces was tried and is wrong. The theory was
|
|
265
|
-
* that an interface the monitor has never heard of comes back `ADAPTER_TYPE_UNKNOWN == 0`
|
|
266
|
-
* and `networkIgnoreMask and 0` is always `0`, making an omitted interface unmaskable. The
|
|
267
|
-
* device log says otherwise: an interface with no monitor entry is dropped from the network
|
|
268
|
-
* list entirely, not admitted as unknown. Here `rmnet_data0` vanishes the moment its entry
|
|
269
|
-
* is gone, while `lo` survives because libwebrtc types loopback from the interface flags
|
|
270
|
-
* and the mask drops it:
|
|
271
|
-
*
|
|
272
|
-
* ```
|
|
273
|
-
* Network connected: NetInfo[name wlan0; handle 0; type 2]
|
|
274
|
-
* Count of networks: 3
|
|
275
|
-
* Net[wlan0:192.168.43.x/24:Wifi:id=5]
|
|
276
|
-
* Net[lo:...:Loopback:id=2]
|
|
277
|
-
* Net[lo:127.0.0.x/8:Loopback:id=1]
|
|
278
|
-
* ```
|
|
279
|
-
*
|
|
280
|
-
* Worse, the decoys were actively fatal. `AndroidNetworkMonitor` keys its networks by
|
|
281
|
-
* *handle*, and both the hotspot and the decoys have to use [UNBINDABLE_HANDLE], so each
|
|
282
|
-
* `onNetworkConnect` overwrote the previous one. Whichever arrived last was the only
|
|
283
|
-
* network the monitor knew, and when that was a masked decoy the answer carried no
|
|
284
|
-
* candidates at all:
|
|
285
|
-
*
|
|
286
|
-
* ```
|
|
287
|
-
* Network connected: NetInfo[name wlan0; handle 0; type 2]
|
|
288
|
-
* Network connected: NetInfo[name rmnet_data0; handle 0; type 4]
|
|
289
|
-
* Count of networks: 4 -> rmnet_data0, rmnet_data0, lo, lo (wlan0 gone)
|
|
290
|
-
* ```
|
|
291
|
-
*
|
|
292
|
-
* One entry, therefore. The invariant stays structural — the only gatherable adapter is the
|
|
293
|
-
* scoped SoftAP — and it no longer depends on a handle being unique.
|
|
294
|
-
*
|
|
295
|
-
* When the hotspot shares an interface *name* with a stock address, the scoped entry wins
|
|
296
|
-
* and carries only its `LinkProperties` addresses. Type cannot separate two addresses on
|
|
297
|
-
* one name, so that residual case is caught by `SoftApSdpGuard` instead.
|
|
298
|
-
*/
|
|
299
|
-
@JvmStatic
|
|
300
|
-
fun mergeScopedNetwork(
|
|
301
|
-
detected: List<NetworkChangeDetector.NetworkInformation>?,
|
|
302
|
-
scoped: ScopedInterface?,
|
|
303
|
-
): List<NetworkChangeDetector.NetworkInformation> {
|
|
304
|
-
val hotspot = scoped?.let { toNetworkInformation(it) } ?: return detected.orEmpty()
|
|
305
|
-
return listOf(hotspot)
|
|
306
|
-
}
|
|
307
|
-
|
|
308
|
-
/** Dotted-quad for the inventory trace. */
|
|
309
|
-
@JvmStatic
|
|
310
|
-
fun formatIpv4(bytes: ByteArray): String =
|
|
311
|
-
bytes.joinToString(".") { (it.toInt() and 0xff).toString() }
|
|
312
|
-
|
|
313
|
-
/** Null when the interface has no address, which libwebrtc cannot use. */
|
|
314
|
-
@JvmStatic
|
|
315
|
-
fun toNetworkInformation(
|
|
316
|
-
scoped: ScopedInterface,
|
|
317
|
-
): NetworkChangeDetector.NetworkInformation? {
|
|
318
|
-
if (scoped.ipv4Addresses.isEmpty()) return null
|
|
319
|
-
val addresses =
|
|
320
|
-
scoped.ipv4Addresses
|
|
321
|
-
.map { NetworkChangeDetector.IPAddress(it) }
|
|
322
|
-
.toTypedArray()
|
|
323
|
-
return NetworkChangeDetector.NetworkInformation(
|
|
324
|
-
scoped.name,
|
|
325
|
-
NetworkChangeDetector.ConnectionType.CONNECTION_WIFI,
|
|
326
|
-
NetworkChangeDetector.ConnectionType.CONNECTION_NONE,
|
|
327
|
-
// The real handle, so BindSocketToNetwork marks ICE sockets onto this Network.
|
|
328
|
-
// Handle 0 advertised the right address and then never completed a check; see the
|
|
329
|
-
// class doc. SoftApSdpGuard.pinHostAddresses puts the scoped IP back in the SDP.
|
|
330
|
-
scoped.networkHandle,
|
|
331
|
-
addresses,
|
|
332
|
-
)
|
|
333
|
-
}
|
|
334
|
-
|
|
335
|
-
/** Resolve the joined SoftAP into the interface libwebrtc should be told about. */
|
|
336
|
-
private fun resolveScopedInterface(
|
|
337
|
-
context: Context,
|
|
338
|
-
scopedNetwork: ScopedSoftApNetwork,
|
|
339
|
-
): ScopedInterface? {
|
|
340
|
-
val network = scopedNetwork.network() ?: return null
|
|
341
|
-
val manager =
|
|
342
|
-
context.getSystemService(ConnectivityManager::class.java) ?: return null
|
|
343
|
-
val properties = manager.getLinkProperties(network) ?: return null
|
|
344
|
-
val interfaceName = properties.interfaceName ?: return null
|
|
345
|
-
// LinkProperties of *this* Network, not NetworkInterface.getByName. On a phone that
|
|
346
|
-
// kept internet Wi-Fi up, wlan0 can carry both 10.x and 192.168.43.x; getByName would
|
|
347
|
-
// hand ICE both, and it just picked the 10.x.
|
|
348
|
-
val addresses =
|
|
349
|
-
properties.linkAddresses
|
|
350
|
-
.mapNotNull { (it.address as? Inet4Address)?.address }
|
|
351
|
-
if (addresses.isEmpty()) return null
|
|
352
|
-
return ScopedInterface(interfaceName, network.networkHandle, addresses)
|
|
353
|
-
}
|
|
354
|
-
}
|
|
355
|
-
}
|
|
@@ -1,109 +0,0 @@
|
|
|
1
|
-
package com.mentra.acsmeeting.network
|
|
2
|
-
|
|
3
|
-
/**
|
|
4
|
-
* Typed failures for a scoped SoftAP join.
|
|
5
|
-
*
|
|
6
|
-
* A denied local-network permission must surface as a distinct, immediate error rather than a
|
|
7
|
-
* 30-second hang that looks like a flaky hotspot. `ACCESS_LOCAL_NETWORK` sits in the nearby-devices
|
|
8
|
-
* group, so it is often already granted during development and easy to miss until a user without it
|
|
9
|
-
* hits a silent timeout.
|
|
10
|
-
*/
|
|
11
|
-
sealed class ScopedNetworkError(val code: String, message: String) : Exception(message) {
|
|
12
|
-
|
|
13
|
-
/** `ACCESS_LOCAL_NETWORK` (or the nearby-devices prerequisite) was not granted. */
|
|
14
|
-
class PermissionDenied(val permission: String) :
|
|
15
|
-
ScopedNetworkError(
|
|
16
|
-
CODE_PERMISSION_DENIED,
|
|
17
|
-
"Local network permission not granted: $permission",
|
|
18
|
-
)
|
|
19
|
-
|
|
20
|
-
/** No usable network arrived before the request timeout. */
|
|
21
|
-
class Timeout(val ssid: String, val timeoutMs: Int) :
|
|
22
|
-
ScopedNetworkError(CODE_TIMEOUT, "Timed out joining $ssid after ${timeoutMs}ms")
|
|
23
|
-
|
|
24
|
-
/**
|
|
25
|
-
* The framework reported it cannot provide this network at all.
|
|
26
|
-
*
|
|
27
|
-
* WifiNetworkSpecifier does not keep scanning for the request timeout. If the glasses AP is
|
|
28
|
-
* not in the current scan — or the user dismissed the system join sheet — this arrives in
|
|
29
|
-
* well under a second.
|
|
30
|
-
*/
|
|
31
|
-
class Unavailable(val ssid: String) :
|
|
32
|
-
ScopedNetworkError(
|
|
33
|
-
CODE_UNAVAILABLE,
|
|
34
|
-
"Could not join $ssid (SSID not in scan, Wi-Fi off, or the system join prompt was dismissed)",
|
|
35
|
-
)
|
|
36
|
-
|
|
37
|
-
/** The network was joined and then went away mid-call. */
|
|
38
|
-
class Lost(val ssid: String) :
|
|
39
|
-
ScopedNetworkError(CODE_LOST, "Lost the connection to $ssid")
|
|
40
|
-
|
|
41
|
-
/** `requestNetwork` itself threw. */
|
|
42
|
-
class RequestFailed(val reason: String) :
|
|
43
|
-
ScopedNetworkError(CODE_REQUEST_FAILED, "Scoped network request failed: $reason")
|
|
44
|
-
|
|
45
|
-
/** Joined, but no IPv4 address was assigned, so nothing can bind to the link. */
|
|
46
|
-
class NoLocalAddress(val ssid: String) :
|
|
47
|
-
ScopedNetworkError(CODE_NO_LOCAL_ADDRESS, "Joined $ssid but no IPv4 address was assigned")
|
|
48
|
-
|
|
49
|
-
/**
|
|
50
|
-
* Joined, but the network never became usable for ICE: a callback never reported, or the
|
|
51
|
-
* address never appeared in the kernel's interface table.
|
|
52
|
-
*
|
|
53
|
-
* Failing here is deliberate. Proceeding produces an ICE answer with no hotspot candidate,
|
|
54
|
-
* which is the same symptom as several unrelated faults and cost real time to attribute.
|
|
55
|
-
*/
|
|
56
|
-
class NotReady(val ssid: String, val missing: String) :
|
|
57
|
-
ScopedNetworkError(
|
|
58
|
-
CODE_NOT_READY,
|
|
59
|
-
"Joined $ssid but the network never became usable: still waiting for $missing",
|
|
60
|
-
)
|
|
61
|
-
|
|
62
|
-
/** Phone Wi-Fi radio is off. WifiNetworkSpecifier then fails as Unavailable in under a second. */
|
|
63
|
-
class WifiDisabled :
|
|
64
|
-
ScopedNetworkError(CODE_WIFI_DISABLED, "Phone Wi-Fi is off; turn it on before joining the glasses hotspot")
|
|
65
|
-
|
|
66
|
-
/**
|
|
67
|
-
* A VPN owns this app's UID. netd then refuses to bind our sockets to the hotspot network
|
|
68
|
-
* (`EPERM`), and the glasses' TCP handshake to the WHIP listener never completes because the
|
|
69
|
-
* reply path is captured by the tunnel. Verified on-device: a shell-UID listener on the same
|
|
70
|
-
* address accepts the glasses' connection while an app-UID listener times out. The scoped join
|
|
71
|
-
* itself succeeds, so without this check the failure only shows up ten seconds into the camera
|
|
72
|
-
* step as an opaque connect timeout.
|
|
73
|
-
*/
|
|
74
|
-
class VpnCapturesApp :
|
|
75
|
-
ScopedNetworkError(
|
|
76
|
-
CODE_VPN_ACTIVE,
|
|
77
|
-
"A VPN is routing this app's traffic, so the glasses cannot reach the phone. " +
|
|
78
|
-
"Exclude Mentra from the VPN (split tunneling) or turn the VPN off, then retry",
|
|
79
|
-
)
|
|
80
|
-
|
|
81
|
-
companion object {
|
|
82
|
-
const val CODE_PERMISSION_DENIED = "SOFTAP_PERMISSION_DENIED"
|
|
83
|
-
const val CODE_TIMEOUT = "SOFTAP_JOIN_TIMEOUT"
|
|
84
|
-
const val CODE_UNAVAILABLE = "SOFTAP_UNAVAILABLE"
|
|
85
|
-
const val CODE_LOST = "SOFTAP_NETWORK_LOST"
|
|
86
|
-
const val CODE_REQUEST_FAILED = "SOFTAP_REQUEST_FAILED"
|
|
87
|
-
const val CODE_NO_LOCAL_ADDRESS = "SOFTAP_NO_LOCAL_ADDRESS"
|
|
88
|
-
const val CODE_NOT_READY = "SOFTAP_NETWORK_NOT_READY"
|
|
89
|
-
const val CODE_WIFI_DISABLED = "SOFTAP_WIFI_DISABLED"
|
|
90
|
-
const val CODE_VPN_ACTIVE = "SOFTAP_VPN_ACTIVE"
|
|
91
|
-
|
|
92
|
-
/** Map a terminal state-machine failure onto the typed error the call path reports. */
|
|
93
|
-
fun from(failure: ScopedNetworkState.Failure, ssid: String, timeoutMs: Int): ScopedNetworkError? =
|
|
94
|
-
when (failure) {
|
|
95
|
-
ScopedNetworkState.Failure.NONE -> null
|
|
96
|
-
ScopedNetworkState.Failure.TIMEOUT -> Timeout(ssid, timeoutMs)
|
|
97
|
-
ScopedNetworkState.Failure.UNAVAILABLE -> Unavailable(ssid)
|
|
98
|
-
ScopedNetworkState.Failure.LOST -> Lost(ssid)
|
|
99
|
-
ScopedNetworkState.Failure.PERMISSION_DENIED -> PermissionDenied(LOCAL_NETWORK_PERMISSION)
|
|
100
|
-
ScopedNetworkState.Failure.REQUEST_FAILED -> RequestFailed("requestNetwork threw")
|
|
101
|
-
}
|
|
102
|
-
|
|
103
|
-
/**
|
|
104
|
-
* Enforced on Android 17+ for apps targeting SDK 37+. Hosts targeting older SDKs
|
|
105
|
-
* retain implicit access through INTERNET and must not request this permission.
|
|
106
|
-
*/
|
|
107
|
-
const val LOCAL_NETWORK_PERMISSION = "android.permission.ACCESS_LOCAL_NETWORK"
|
|
108
|
-
}
|
|
109
|
-
}
|
|
@@ -1,160 +0,0 @@
|
|
|
1
|
-
package com.mentra.acsmeeting.network
|
|
2
|
-
|
|
3
|
-
import com.mentra.acsmeeting.trace.SoftApTrace
|
|
4
|
-
import org.webrtc.NetworkChangeDetector
|
|
5
|
-
|
|
6
|
-
/**
|
|
7
|
-
* Owns libwebrtc's network state for as long as a scoped SoftAP is joined, and gives it back after.
|
|
8
|
-
*
|
|
9
|
-
* `getActiveNetworkList` is only a snapshot. libwebrtc's process-wide monitor also keeps every
|
|
10
|
-
* network it has ever been told about through [onNetworkConnect]. AutoDetect is constructed with
|
|
11
|
-
* this observer and reports cellular (and leftover home Wi-Fi) throughout the ACS join that happens
|
|
12
|
-
* *before* ingest starts, and those handles stay in the native list. Gathering unions the snapshot
|
|
13
|
-
* with that history, which is how a correct inventory still produced a lone cellular candidate.
|
|
14
|
-
*
|
|
15
|
-
* ## Ownership, not filtering
|
|
16
|
-
*
|
|
17
|
-
* The monitor is process-wide and the libwebrtc AAR is deliberately shared with LiveKit, so this
|
|
18
|
-
* cannot simply mute callbacks and walk away. The contract is explicit:
|
|
19
|
-
*
|
|
20
|
-
* - **While SoftAP is joined** this observer owns the monitor. Stock connect/disconnect callbacks
|
|
21
|
-
* are suppressed (but *recorded*), every handle outside the SoftAP inventory is disconnected,
|
|
22
|
-
* and exactly that inventory is connected.
|
|
23
|
-
* - **When the hotspot goes away** ownership is handed back by [restore]: the synthetic entry is
|
|
24
|
-
* disconnected and every stock network whose callback was suppressed is replayed, so a WHEP or
|
|
25
|
-
* LiveKit connection started afterwards sees real networks. Resuming pass-through alone is not
|
|
26
|
-
* enough — the networks it never heard about would stay missing until the OS happened to
|
|
27
|
-
* re-announce them.
|
|
28
|
-
*
|
|
29
|
-
* Restoration is driven from both directions, because either may come first: [publish] runs on
|
|
30
|
-
* every inventory read, and each stock callback checks too, since after teardown the OS may be the
|
|
31
|
-
* only thing still talking to us.
|
|
32
|
-
*/
|
|
33
|
-
internal class ScopedNetworkObserver(
|
|
34
|
-
private val downstream: NetworkChangeDetector.Observer,
|
|
35
|
-
private val scoped: () -> ScopedNetworkChangeDetector.ScopedInterface?,
|
|
36
|
-
) : NetworkChangeDetector.Observer() {
|
|
37
|
-
|
|
38
|
-
private val lock = Any()
|
|
39
|
-
|
|
40
|
-
/**
|
|
41
|
-
* Stock networks by handle, kept whole rather than as bare handles: replaying a suppressed
|
|
42
|
-
* network on teardown requires the [NetworkChangeDetector.NetworkInformation] verbatim.
|
|
43
|
-
*/
|
|
44
|
-
private val stock = linkedMapOf<Long, NetworkChangeDetector.NetworkInformation>()
|
|
45
|
-
|
|
46
|
-
private val published = mutableSetOf<Long>()
|
|
47
|
-
private var lastSignature: String? = null
|
|
48
|
-
|
|
49
|
-
/** True while this observer has taken over the monitor and still owes a [restore]. */
|
|
50
|
-
private var owning = false
|
|
51
|
-
|
|
52
|
-
/**
|
|
53
|
-
* Make native state match [inventory] whenever SoftAP is up. A no-op when the snapshot has not
|
|
54
|
-
* changed, so [ScopedNetworkChangeDetector.getActiveNetworkList] can call this on every read.
|
|
55
|
-
*/
|
|
56
|
-
fun publish(inventory: List<NetworkChangeDetector.NetworkInformation>) {
|
|
57
|
-
if (scoped() == null) {
|
|
58
|
-
restore()
|
|
59
|
-
return
|
|
60
|
-
}
|
|
61
|
-
val signature =
|
|
62
|
-
inventory.joinToString("|") { info ->
|
|
63
|
-
val ips =
|
|
64
|
-
info.ipAddresses.joinToString("/") { address ->
|
|
65
|
-
ScopedNetworkChangeDetector.formatIpv4(address.address)
|
|
66
|
-
}
|
|
67
|
-
"${info.handle}:${info.name}:$ips"
|
|
68
|
-
}
|
|
69
|
-
synchronized(lock) {
|
|
70
|
-
if (signature == lastSignature) return
|
|
71
|
-
lastSignature = signature
|
|
72
|
-
owning = true
|
|
73
|
-
|
|
74
|
-
val keep = inventory.map { it.handle }.toSet()
|
|
75
|
-
// Stock handles announced *before* the join are the ones that poison gathering, so
|
|
76
|
-
// they are disconnected here even though their callbacks were never forwarded.
|
|
77
|
-
val dropped = (stock.keys + published).filterNot { it in keep }
|
|
78
|
-
dropped.forEach { handle -> downstream.onNetworkDisconnect(handle) }
|
|
79
|
-
published.clear()
|
|
80
|
-
inventory.forEach { info ->
|
|
81
|
-
published.add(info.handle)
|
|
82
|
-
downstream.onNetworkConnect(info)
|
|
83
|
-
}
|
|
84
|
-
SoftApTrace.stage(
|
|
85
|
-
"webrtc_network_reconciled",
|
|
86
|
-
"published" to inventory.size,
|
|
87
|
-
"dropped" to dropped.size,
|
|
88
|
-
)
|
|
89
|
-
}
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
/**
|
|
93
|
-
* Hand the monitor back: drop the synthetic entry, replay every suppressed stock network.
|
|
94
|
-
*
|
|
95
|
-
* Idempotent, and safe to call when this observer never took ownership.
|
|
96
|
-
*/
|
|
97
|
-
fun restore() {
|
|
98
|
-
synchronized(lock) {
|
|
99
|
-
if (!owning) return
|
|
100
|
-
owning = false
|
|
101
|
-
lastSignature = null
|
|
102
|
-
|
|
103
|
-
val synthetic = published.toList()
|
|
104
|
-
published.clear()
|
|
105
|
-
synthetic.forEach { handle -> downstream.onNetworkDisconnect(handle) }
|
|
106
|
-
// Replay rather than wait: these networks exist, and the monitor was told they did not.
|
|
107
|
-
stock.values.forEach { info -> downstream.onNetworkConnect(info) }
|
|
108
|
-
SoftApTrace.stage(
|
|
109
|
-
"webrtc_network_restored",
|
|
110
|
-
"syntheticDropped" to synthetic.size,
|
|
111
|
-
"stockReplayed" to stock.size,
|
|
112
|
-
)
|
|
113
|
-
}
|
|
114
|
-
}
|
|
115
|
-
|
|
116
|
-
override fun onConnectionTypeChanged(connectionType: NetworkChangeDetector.ConnectionType) {
|
|
117
|
-
if (scoped() != null) {
|
|
118
|
-
downstream.onConnectionTypeChanged(NetworkChangeDetector.ConnectionType.CONNECTION_WIFI)
|
|
119
|
-
return
|
|
120
|
-
}
|
|
121
|
-
restore()
|
|
122
|
-
downstream.onConnectionTypeChanged(connectionType)
|
|
123
|
-
}
|
|
124
|
-
|
|
125
|
-
override fun onNetworkConnect(networkInfo: NetworkChangeDetector.NetworkInformation) {
|
|
126
|
-
if (scoped() != null) {
|
|
127
|
-
synchronized(lock) { stock[networkInfo.handle] = networkInfo }
|
|
128
|
-
SoftApTrace.stage(
|
|
129
|
-
"webrtc_stock_connect_suppressed",
|
|
130
|
-
"name" to networkInfo.name,
|
|
131
|
-
"handle" to networkInfo.handle,
|
|
132
|
-
)
|
|
133
|
-
return
|
|
134
|
-
}
|
|
135
|
-
// Restore before recording, so this network is forwarded once below rather than also
|
|
136
|
-
// arriving in the replay.
|
|
137
|
-
restore()
|
|
138
|
-
synchronized(lock) { stock[networkInfo.handle] = networkInfo }
|
|
139
|
-
downstream.onNetworkConnect(networkInfo)
|
|
140
|
-
}
|
|
141
|
-
|
|
142
|
-
override fun onNetworkDisconnect(networkHandle: Long) {
|
|
143
|
-
if (scoped() != null) {
|
|
144
|
-
synchronized(lock) { stock.remove(networkHandle) }
|
|
145
|
-
return
|
|
146
|
-
}
|
|
147
|
-
restore()
|
|
148
|
-
synchronized(lock) { stock.remove(networkHandle) }
|
|
149
|
-
downstream.onNetworkDisconnect(networkHandle)
|
|
150
|
-
}
|
|
151
|
-
|
|
152
|
-
override fun onNetworkPreference(
|
|
153
|
-
types: List<NetworkChangeDetector.ConnectionType>,
|
|
154
|
-
preference: Int,
|
|
155
|
-
) {
|
|
156
|
-
if (scoped() != null) return
|
|
157
|
-
restore()
|
|
158
|
-
downstream.onNetworkPreference(types, preference)
|
|
159
|
-
}
|
|
160
|
-
}
|