@glassly/bluetooth-sdk 0.1.0-dev.98 → 0.2.8-dev.444
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 +47 -6
- package/android/build.gradle +7 -7
- package/android/lc3Lib/build.gradle +2 -2
- package/android/src/main/assets/G2Firmware/README.md +27 -2
- package/android/src/main/assets/G2Firmware/{g2-stock-2.2.9.22.evenota.bin → g2-glassly-cfw-2.3.0.24.evenota.bin} +0 -0
- package/android/src/main/assets/G2Firmware/{g2-glassly-cfw-2.2.9.22.evenota.bin → g2-stock-2.3.0.24.evenota.bin} +0 -0
- package/android/src/main/java/com/glassly/bluetoothsdk/BluetoothSdk.kt +38 -0
- package/android/src/main/java/com/glassly/bluetoothsdk/BluetoothSdkModule.kt +50 -14
- package/android/src/main/java/com/glassly/bluetoothsdk/Bridge.kt +18 -0
- package/android/src/main/java/com/glassly/bluetoothsdk/DeviceManager.kt +139 -10
- package/android/src/main/java/com/glassly/bluetoothsdk/DeviceStore.kt +28 -1
- package/android/src/main/java/com/glassly/bluetoothsdk/DisplayPacer.kt +199 -0
- package/android/src/main/java/com/glassly/bluetoothsdk/GeneratedChangelogCatalog.kt +10 -1
- package/android/src/main/java/com/glassly/bluetoothsdk/GeneratedReleaseMetadata.kt +4 -4
- package/android/src/main/java/com/glassly/bluetoothsdk/GlassesNotifications.kt +155 -0
- package/android/src/main/java/com/glassly/bluetoothsdk/sgcs/G2.kt +1075 -257
- package/android/src/main/java/com/glassly/bluetoothsdk/sgcs/G2BleLink.kt +119 -0
- package/android/src/main/java/com/glassly/bluetoothsdk/sgcs/G2CfwClassification.kt +29 -28
- package/android/src/main/java/com/glassly/bluetoothsdk/sgcs/G2CfwRenderer.kt +138 -33
- package/android/src/main/java/com/glassly/bluetoothsdk/sgcs/G2CfwScene.kt +673 -0
- package/android/src/main/java/com/glassly/bluetoothsdk/sgcs/G2CfwTextureCache.kt +235 -162
- package/android/src/main/java/com/glassly/bluetoothsdk/sgcs/G2CfwTransport.kt +402 -0
- package/android/src/main/java/com/glassly/bluetoothsdk/sgcs/G2DisplayDiagnostics.kt +84 -0
- package/android/src/main/java/com/glassly/bluetoothsdk/sgcs/G2FlashProtocol.kt +3 -3
- package/android/src/main/java/com/glassly/bluetoothsdk/sgcs/G2Flasher.kt +9 -9
- package/android/src/main/java/com/glassly/bluetoothsdk/sgcs/G2Keyboard.kt +689 -0
- package/android/src/main/java/com/glassly/bluetoothsdk/sgcs/G2KeyboardBridge.kt +452 -0
- package/android/src/main/java/com/glassly/bluetoothsdk/sgcs/G2KeyboardHid.kt +208 -0
- package/android/src/main/java/com/glassly/bluetoothsdk/sgcs/G2NotificationFile.kt +181 -0
- package/android/src/main/java/com/glassly/bluetoothsdk/sgcs/G2RingBattery.kt +20 -0
- package/android/src/main/java/com/glassly/bluetoothsdk/sgcs/G2Sensors.kt +67 -0
- package/android/src/main/java/com/glassly/bluetoothsdk/sgcs/G2TouchInput.kt +39 -0
- package/android/src/main/java/com/glassly/bluetoothsdk/sgcs/MentraLive.kt +5 -5
- package/android/src/main/java/com/glassly/bluetoothsdk/sgcs/MentraNex.kt +14 -14
- package/android/src/main/java/com/glassly/bluetoothsdk/sgcs/{GlasslyBle.java → NexProto.java} +6462 -7618
- package/android/src/main/java/com/glassly/bluetoothsdk/sgcs/NotificationMessageIds.kt +58 -0
- package/android/src/main/java/com/glassly/bluetoothsdk/sgcs/SGCManager.kt +77 -2
- package/android/src/main/java/com/glassly/bluetoothsdk/speakerid/SpeakerIdEngine.kt +1 -1
- package/android/src/main/java/com/glassly/bluetoothsdk/status/DeviceStatus.kt +55 -0
- package/android/src/main/java/com/glassly/bluetoothsdk/stt/STTTools.kt +1 -1
- package/android/src/main/java/com/glassly/bluetoothsdk/stt/SherpaOnnxTranscriber.kt +1 -1
- package/android/src/main/java/com/glassly/bluetoothsdk/tts/TTSTools.kt +1 -1
- package/android/src/main/java/com/glassly/bluetoothsdk/utils/NexSGCUtils.kt +24 -149
- package/android/src/test/java/com/glassly/bluetoothsdk/DisplayPacerTest.kt +277 -0
- package/android/src/test/java/com/glassly/bluetoothsdk/G2BleLinkTest.kt +70 -0
- package/android/src/test/java/com/glassly/bluetoothsdk/sgcs/G2CfwClassificationTest.kt +45 -56
- package/android/src/test/java/com/glassly/bluetoothsdk/sgcs/G2CfwRendererTest.kt +78 -5
- package/android/src/test/java/com/glassly/bluetoothsdk/sgcs/G2CfwSceneTest.kt +489 -0
- package/android/src/test/java/com/glassly/bluetoothsdk/sgcs/G2CfwTextureCacheTest.kt +100 -43
- package/android/src/test/java/com/glassly/bluetoothsdk/sgcs/G2CfwTransportTest.kt +252 -0
- package/android/src/test/java/com/glassly/bluetoothsdk/sgcs/G2DisplayDiagnosticsTest.kt +64 -0
- package/android/src/test/java/com/glassly/bluetoothsdk/sgcs/G2KeyboardTest.kt +271 -0
- package/android/src/test/java/com/glassly/bluetoothsdk/sgcs/G2NotificationFileTest.kt +204 -0
- package/android/src/test/java/com/glassly/bluetoothsdk/sgcs/G2RingBatteryTest.kt +56 -0
- package/android/src/test/java/com/glassly/bluetoothsdk/sgcs/G2SensorsTest.kt +56 -0
- package/android/src/test/java/com/glassly/bluetoothsdk/sgcs/G2TouchInputTest.kt +60 -0
- package/android/src/test/java/com/glassly/bluetoothsdk/sgcs/NexProtoDescriptorTest.kt +32 -0
- package/android/src/test/java/com/glassly/bluetoothsdk/sgcs/NotificationMessageIdsTest.kt +58 -0
- package/build/BluetoothSdk.types.d.ts +209 -20
- package/build/BluetoothSdk.types.d.ts.map +1 -1
- package/build/BluetoothSdk.types.js.map +1 -1
- package/build/_internal.d.ts +1 -1
- package/build/_internal.d.ts.map +1 -1
- package/build/_internal.js.map +1 -1
- package/build/_private/BluetoothSdkModule.d.ts +30 -1
- package/build/_private/BluetoothSdkModule.d.ts.map +1 -1
- package/build/_private/BluetoothSdkModule.js.map +1 -1
- package/build/generated/changelogCatalog.d.ts +28 -1
- package/build/generated/changelogCatalog.d.ts.map +1 -1
- package/build/generated/changelogCatalog.js +37 -1
- package/build/generated/changelogCatalog.js.map +1 -1
- package/build/generated/releaseMetadata.js +4 -4
- package/build/generated/releaseMetadata.js.map +1 -1
- package/build/index.d.ts +1 -1
- package/build/index.d.ts.map +1 -1
- package/build/index.js +14 -0
- package/build/index.js.map +1 -1
- package/ios/BluetoothSdkModule.swift +102 -13
- package/ios/GlasslyBluetoothSDK.podspec +1 -1
- package/ios/Packages/SherpaOnnx/SherpaOnnx.swift +8 -3
- package/ios/Source/BluetoothSDK.swift +53 -0
- package/ios/Source/BluetoothSdkDefaults.swift +1 -1
- package/ios/Source/Bridge.swift +13 -0
- package/ios/Source/DeviceManager.swift +151 -33
- package/ios/Source/DeviceStore.swift +28 -14
- package/ios/Source/DisplayPacer.swift +180 -0
- package/ios/Source/GeneratedChangelogCatalog.swift +10 -1
- package/ios/Source/GeneratedReleaseMetadata.swift +4 -4
- package/ios/Source/GlassesNotifications.swift +181 -0
- package/ios/Source/ObservableStore.swift +14 -2
- package/ios/Source/Resources/G2Firmware/README.md +27 -2
- package/ios/Source/Resources/G2Firmware/{g2-stock-2.2.9.22.evenota.bin → g2-glassly-cfw-2.3.0.24.evenota.bin} +0 -0
- package/ios/Source/Resources/G2Firmware/{g2-glassly-cfw-2.2.9.22.evenota.bin → g2-stock-2.3.0.24.evenota.bin} +0 -0
- package/ios/Source/controllers/R1.swift +0 -1
- package/ios/Source/internal/BluetoothAvailability.swift +31 -0
- package/ios/Source/sgcs/G2.swift +1434 -504
- package/ios/Source/sgcs/G2AncsRelay.swift +268 -0
- package/ios/Source/sgcs/G2BleLink.swift +102 -0
- package/ios/Source/sgcs/G2CfwClassification.swift +45 -27
- package/ios/Source/sgcs/G2CfwRenderer.swift +166 -33
- package/ios/Source/sgcs/G2CfwScene.swift +779 -0
- package/ios/Source/sgcs/G2CfwTextureCache.swift +244 -117
- package/ios/Source/sgcs/G2CfwTransport.swift +485 -0
- package/ios/Source/sgcs/G2DisplayDiagnostics.swift +154 -0
- package/ios/Source/sgcs/G2FlashProtocol.swift +2 -2
- package/ios/Source/sgcs/G2Flasher.swift +9 -9
- package/ios/Source/sgcs/G2Keyboard.swift +780 -0
- package/ios/Source/sgcs/G2KeyboardBridge.swift +438 -0
- package/ios/Source/sgcs/G2KeyboardHid.swift +290 -0
- package/ios/Source/sgcs/G2NotificationTuning.swift +24 -0
- package/ios/Source/sgcs/G2RingBattery.swift +22 -0
- package/ios/Source/sgcs/G2Sensors.swift +75 -0
- package/ios/Source/sgcs/G2TouchInput.swift +51 -0
- package/ios/Source/sgcs/G2WritePacing.swift +18 -0
- package/ios/Source/sgcs/MentraNex.swift +44 -44
- package/ios/Source/sgcs/SGCManager.swift +120 -13
- package/ios/Source/sgcs/{glassly_ble.pb.swift → nex_ble.pb.swift} +405 -465
- package/ios/Source/speakerid/SpeakerIdEngine.swift +2 -2
- package/ios/Source/status/DeviceStatus.swift +27 -0
- package/ios/Source/stt/SherpaOnnxTranscriber.swift +15 -14
- package/ios/Source/utils/Enums.swift +0 -1
- package/ios/Source/utils/G1Text.swift +0 -1
- package/ios/Source/utils/G2NotifyFile.swift +20 -10
- package/ios/Source/utils/JSCExperiment.swift +6 -6
- package/ios/Source/utils/Models.swift +0 -1
- package/ios/Tests/BluetoothSDKTests/BluetoothAvailabilityTests.swift +29 -0
- package/ios/Tests/BluetoothSDKTests/DisplayPacerTests.swift +255 -0
- package/ios/Tests/BluetoothSDKTests/G2AncsRelayTests.swift +186 -0
- package/ios/Tests/BluetoothSDKTests/G2BleLinkTests.swift +52 -0
- package/ios/Tests/BluetoothSDKTests/G2CfwClassificationTests.swift +46 -51
- package/ios/Tests/BluetoothSDKTests/G2CfwRendererTests.swift +164 -5
- package/ios/Tests/BluetoothSDKTests/G2CfwSceneTests.swift +497 -0
- package/ios/Tests/BluetoothSDKTests/G2CfwTextureCacheTests.swift +108 -44
- package/ios/Tests/BluetoothSDKTests/G2CfwTransportTests.swift +259 -0
- package/ios/Tests/BluetoothSDKTests/G2DisplayDiagnosticsTests.swift +89 -0
- package/ios/Tests/BluetoothSDKTests/G2FlashProtocolTests.swift +3 -2
- package/ios/Tests/BluetoothSDKTests/G2ImageAckTests.swift +104 -0
- package/ios/Tests/BluetoothSDKTests/G2KeyboardTests.swift +274 -0
- package/ios/Tests/BluetoothSDKTests/G2NotifyFileTests.swift +21 -5
- package/ios/Tests/BluetoothSDKTests/G2RingBatteryTests.swift +56 -0
- package/ios/Tests/BluetoothSDKTests/G2SensorsTests.swift +56 -0
- package/ios/Tests/BluetoothSDKTests/G2TouchInputTests.swift +71 -0
- package/ios/Tests/BluetoothSDKTests/G2WritePacingTests.swift +35 -0
- package/ios/Tests/BluetoothSDKTests/GlassesNotificationTests.swift +95 -0
- package/package.json +1 -1
- package/plugin/build/withAndroid.js +2 -2
- package/plugin/build/withIos.js +1 -1
- package/scripts/gen-nex-proto.sh +12 -0
- package/scripts/inject-ios-sdk-version.mjs +1 -1
- package/src/BluetoothSdk.types.ts +214 -19
- package/src/_internal.ts +1 -1
- package/src/_private/BluetoothSdkModule.ts +40 -0
- package/src/generated/changelogCatalog.ts +37 -1
- package/src/generated/releaseMetadata.ts +4 -4
- package/src/index.ts +24 -0
|
@@ -35,46 +35,49 @@ class G2CfwTextureCacheTest {
|
|
|
35
35
|
)
|
|
36
36
|
}
|
|
37
37
|
|
|
38
|
-
|
|
38
|
+
private fun u32(v: Int): List<Int> = listOf(v and 0xFF, (v ushr 8) and 0xFF, (v ushr 16) and 0xFF, (v ushr 24) and 0xFF)
|
|
39
|
+
|
|
40
|
+
// ── Asset mirror ────────────────────────────────────────────────────────
|
|
39
41
|
|
|
40
42
|
@Test
|
|
41
|
-
fun
|
|
43
|
+
fun fontsUploadWholeAsOneAssetWithRelativeGlyphOffsets() {
|
|
42
44
|
val cache = G2CfwTextureCache()
|
|
43
45
|
cache.reset(1)
|
|
44
|
-
val
|
|
46
|
+
val raster = ByteArray(8) { 0xFF.toByte() } // 4×2 white cell
|
|
47
|
+
var rasterized = 0
|
|
48
|
+
val font = cache.font(fontSize = 12, cellWidth = 4, lineHeight = 2, codes = listOf(65, 66)) { code ->
|
|
49
|
+
rasterized += 1
|
|
50
|
+
if (code == 65) raster else null
|
|
51
|
+
}
|
|
45
52
|
assertNotNull(font)
|
|
46
53
|
font!!
|
|
47
|
-
|
|
48
|
-
assertEquals(
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
// Second ensure is a no-op (no duplicate uploads).
|
|
53
|
-
assertTrue(cache.ensureGlyph(font, 'A'.code) { raster })
|
|
54
|
+
assertEquals(2, rasterized)
|
|
55
|
+
assertEquals(setOf(65), font.glyphs)
|
|
56
|
+
assertEquals(G2CfwTextureCache.hash31("font:12:4x2:ascii"), font.id)
|
|
57
|
+
assertTrue(cache.font(12, 4, 2, codes = listOf(65, 66)) { rasterized += 1; null } === font)
|
|
58
|
+
assertEquals(2, rasterized)
|
|
54
59
|
|
|
55
60
|
val payloads = cache.takeUploadPayloads()
|
|
56
61
|
assertEquals(1, payloads.size)
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
)
|
|
71
|
-
|
|
62
|
+
val p = payloads[0].map { it.toInt() and 0xFF }
|
|
63
|
+
assertEquals(listOf(37, 0, 0, 0, 0), p.subList(0, 5))
|
|
64
|
+
assertEquals(0, p[5])
|
|
65
|
+
assertEquals(u32(font.id), p.subList(6, 10))
|
|
66
|
+
assertEquals(G2CfwTextureCache.KIND_FONT, p[14])
|
|
67
|
+
val total = 96 * 4 + 2 + 1
|
|
68
|
+
assertEquals(u32(total), p.subList(15, 19))
|
|
69
|
+
assertEquals(u32(0), p.subList(19, 23))
|
|
70
|
+
assertEquals(listOf(total and 0xFF, total ushr 8), p.subList(23, 25))
|
|
71
|
+
val table = p.subList(25, 25 + 96 * 4)
|
|
72
|
+
assertEquals(u32(96 * 4), table.subList((65 - 32) * 4, (65 - 32) * 4 + 4))
|
|
73
|
+
assertEquals(listOf(0, 0, 0, 0), table.subList((66 - 32) * 4, (66 - 32) * 4 + 4))
|
|
74
|
+
assertEquals(listOf(4, 2, 0x8F), p.subList(25 + 96 * 4, 25 + total))
|
|
72
75
|
assertTrue(cache.takeUploadPayloads().isEmpty())
|
|
76
|
+
cache.forget(listOf(font.id))
|
|
77
|
+
cache.font(12, 4, 2, codes = listOf(65)) { raster }
|
|
78
|
+
assertEquals(1, cache.takeUploadPayloads().size)
|
|
73
79
|
cache.reset(2)
|
|
74
|
-
|
|
75
|
-
val font2 = cache.font(fontSize = 12, cellWidth = 4, lineHeight = 2)
|
|
76
|
-
assertNotNull(font2)
|
|
77
|
-
assertTrue(cache.ensureGlyph(font2!!, 'A'.code) { raster })
|
|
80
|
+
cache.font(12, 4, 2, codes = listOf(65)) { raster }
|
|
78
81
|
assertEquals(1, cache.takeUploadPayloads().size)
|
|
79
82
|
}
|
|
80
83
|
|
|
@@ -85,37 +88,91 @@ class G2CfwTextureCacheTest {
|
|
|
85
88
|
assertFalse(cache.noteImageSighting("icon@10x10"))
|
|
86
89
|
assertTrue(cache.noteImageSighting("icon@10x10"))
|
|
87
90
|
val gray = ByteArray(100) { 0x80.toByte() }
|
|
88
|
-
val
|
|
89
|
-
|
|
90
|
-
assertEquals(
|
|
91
|
+
val id = cache.ensureImage("icon@10x10", gray, width = 10, height = 10)
|
|
92
|
+
assertEquals(G2CfwTextureCache.hash31("icon@10x10"), id)
|
|
93
|
+
assertEquals(id, cache.ensureImage("icon@10x10", gray, width = 10, height = 10))
|
|
91
94
|
assertNull(cache.ensureImage("big", ByteArray(300 * 10), width = 300, height = 10))
|
|
95
|
+
assertEquals(1, cache.takeUploadPayloads().size)
|
|
96
|
+
assertTrue(cache.holds(id!!))
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
@Test
|
|
100
|
+
fun stringsAreBoundedAndLargeAssetsAreChunked() {
|
|
101
|
+
val cache = G2CfwTextureCache()
|
|
102
|
+
cache.reset(1)
|
|
103
|
+
assertNull(cache.ensureBytes("empty", ByteArray(0)))
|
|
104
|
+
assertNull(cache.ensureBytes("nul", byteArrayOf(65, 0)))
|
|
105
|
+
assertNull(cache.ensureBytes("long", ByteArray(129) { 65 }))
|
|
106
|
+
assertNotNull(cache.ensureBytes("ok", ByteArray(128) { 65 }))
|
|
107
|
+
val entries = G2CfwTextureCache.putAssetEntries(7, 9, 0, ByteArray(20000) { 1 })
|
|
108
|
+
assertEquals(3, entries.size)
|
|
109
|
+
var offset = 0
|
|
110
|
+
for (entry in entries) {
|
|
111
|
+
val e = entry.map { it.toInt() and 0xFF }
|
|
112
|
+
assertEquals(u32(20000), e.subList(10, 14))
|
|
113
|
+
assertEquals(u32(offset), e.subList(14, 18))
|
|
114
|
+
val len = e[18] or (e[19] shl 8)
|
|
115
|
+
assertEquals(20 + len, e.size)
|
|
116
|
+
offset += len
|
|
117
|
+
}
|
|
118
|
+
assertEquals(20000, offset)
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
@Test
|
|
122
|
+
fun uploadPayloadsNameTheAssetsTheyPut() {
|
|
123
|
+
val cache = G2CfwTextureCache()
|
|
124
|
+
cache.reset(1)
|
|
125
|
+
val noise = ByteArray(255 * 255) { (it * 37).toByte() }
|
|
126
|
+
val a = cache.ensureBytes("a", byteArrayOf(65, 66))!!
|
|
127
|
+
val big = cache.ensureImage("noise", noise, 255, 255)!!
|
|
128
|
+
val b = cache.ensureBytes("b", byteArrayOf(67))!!
|
|
129
|
+
val payloads = cache.takeUploadPayloads()
|
|
130
|
+
assertTrue("the image spans several payloads", payloads.size > 1)
|
|
131
|
+
val ids = payloads.map { G2CfwTextureCache.assetIdsInUploadPayload(it) }
|
|
132
|
+
assertEquals(setOf(a, big, b), ids.flatten().toSet())
|
|
133
|
+
assertEquals(a, ids.first().first())
|
|
134
|
+
assertEquals(b, ids.last().last())
|
|
135
|
+
assertTrue(G2CfwTextureCache.assetIdsInUploadPayload(byteArrayOf(38, 0, 0, 0, 0, 0)).isEmpty())
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
@Test
|
|
139
|
+
fun predictedOverflowForgetsTheLeastRecentlyUsedAssets() {
|
|
140
|
+
val cache = G2CfwTextureCache()
|
|
141
|
+
cache.reset(1)
|
|
142
|
+
val noise = ByteArray(255 * 255) { (it * 37).toByte() }
|
|
143
|
+
val ids = (0 until 5).map { cache.ensureImage("n$it", noise, 255, 255)!! }
|
|
144
|
+
assertTrue(cache.exhausted)
|
|
145
|
+
assertFalse(cache.holds(ids[0]))
|
|
146
|
+
assertTrue(cache.holds(ids[4]))
|
|
147
|
+
assertTrue(cache.predictedBytes <= G2CfwTextureCache.STORE_SIZE)
|
|
92
148
|
}
|
|
93
149
|
|
|
94
150
|
// ── Draw payload wire formats ───────────────────────────────────────────
|
|
95
151
|
|
|
96
152
|
@Test
|
|
97
|
-
fun
|
|
98
|
-
val payload = G2CfwTextureCache.imageDrawPayload(
|
|
153
|
+
fun imageDrawPayloadMatchesMode19Wire() {
|
|
154
|
+
val payload = G2CfwTextureCache.imageDrawPayload(assetId = 0x1234, x = 300, y = -2, options = 0x0F)
|
|
99
155
|
assertNotNull(payload)
|
|
100
156
|
assertArrayEquals(
|
|
101
|
-
byteArrayOf(
|
|
157
|
+
byteArrayOf(19, 0x34, 0x12, 0, 0, 0x2C, 0x01, 0xFE.toByte(), 0xFF.toByte(), 0x0F),
|
|
102
158
|
payload,
|
|
103
159
|
)
|
|
160
|
+
assertNull(G2CfwTextureCache.imageDrawPayload(assetId = 0, x = 0, y = 0, options = 0))
|
|
104
161
|
}
|
|
105
162
|
|
|
106
163
|
@Test
|
|
107
|
-
fun
|
|
164
|
+
fun stringRunPayloadMatchesMode20Wire() {
|
|
108
165
|
val chars = "Hi".toByteArray(Charsets.US_ASCII)
|
|
109
|
-
val payload =
|
|
110
|
-
G2CfwTextureCache.stringRunPayload(fontTableOffset = 2, x = 40, y = 150, options = 0x0F, chars = chars)
|
|
166
|
+
val payload = G2CfwTextureCache.stringRunPayload(fontId = 2, x = 40, y = 150, options = 0x0F, chars = chars)
|
|
111
167
|
assertNotNull(payload)
|
|
112
168
|
assertArrayEquals(
|
|
113
|
-
byteArrayOf(
|
|
169
|
+
byteArrayOf(20, 2, 0, 0, 0, 40, 0, 150.toByte(), 0, 0x0F, 2) + chars,
|
|
114
170
|
payload,
|
|
115
171
|
)
|
|
116
172
|
// Control/non-ASCII bytes are x-adjust commands on the wire — rejected.
|
|
117
173
|
assertNull(G2CfwTextureCache.stringRunPayload(2, 0, 0, 0, byteArrayOf(10)))
|
|
118
174
|
assertNull(G2CfwTextureCache.stringRunPayload(2, 0, 0, 0, byteArrayOf(200.toByte())))
|
|
175
|
+
assertNull(G2CfwTextureCache.stringRunPayload(0, 0, 0, 0, chars))
|
|
119
176
|
}
|
|
120
177
|
|
|
121
178
|
// ── End-to-end mono fast path ───────────────────────────────────────────
|
|
@@ -162,10 +219,10 @@ class G2CfwTextureCacheTest {
|
|
|
162
219
|
assertNotNull(second)
|
|
163
220
|
second!!
|
|
164
221
|
assertFalse(second.usedFullFrame)
|
|
165
|
-
// The changed cells travel as
|
|
222
|
+
// The changed cells travel as a font-asset PUT + mode-20 runs; no
|
|
166
223
|
// bitmap delta (0x83) should remain for a pure-ASCII grid change.
|
|
167
224
|
assertTrue(second.uploads.isNotEmpty())
|
|
168
|
-
assertTrue(second.uploads.all { it.first() ==
|
|
225
|
+
assertTrue(second.uploads.all { it.first() == 37.toByte() })
|
|
169
226
|
val payload = second.payload
|
|
170
227
|
assertNotNull(payload)
|
|
171
228
|
payload!!
|
|
@@ -178,10 +235,10 @@ class G2CfwTextureCacheTest {
|
|
|
178
235
|
if (pos < payload.size) segments.add(payload[pos])
|
|
179
236
|
pos += len
|
|
180
237
|
}
|
|
181
|
-
assertTrue(segments.contains(
|
|
238
|
+
assertTrue(segments.contains(20.toByte()))
|
|
182
239
|
assertFalse(segments.contains(0x83.toByte()))
|
|
183
240
|
} else {
|
|
184
|
-
assertEquals(
|
|
241
|
+
assertEquals(20.toByte(), payload.first())
|
|
185
242
|
}
|
|
186
243
|
|
|
187
244
|
// A lens-unequal window must fall back to plain bitmap deltas.
|
|
@@ -0,0 +1,252 @@
|
|
|
1
|
+
package com.glassly.bluetoothsdk.sgcs
|
|
2
|
+
|
|
3
|
+
import java.util.zip.Inflater
|
|
4
|
+
import kotlinx.coroutines.runBlocking
|
|
5
|
+
import kotlinx.coroutines.withTimeoutOrNull
|
|
6
|
+
import org.junit.Assert.assertArrayEquals
|
|
7
|
+
import org.junit.Assert.assertEquals
|
|
8
|
+
import org.junit.Assert.assertFalse
|
|
9
|
+
import org.junit.Assert.assertNotNull
|
|
10
|
+
import org.junit.Assert.assertNull
|
|
11
|
+
import org.junit.Assert.assertTrue
|
|
12
|
+
import org.junit.Test
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Wire contract of the SID-0xf0 message stream (g2flash message_transport.c,
|
|
16
|
+
* GLASSLYCFW/31). The probe in g2flash (`send_message_probe.py --dry-run`)
|
|
17
|
+
* produces the same packets for the same input.
|
|
18
|
+
*/
|
|
19
|
+
class G2CfwTransportTest {
|
|
20
|
+
private fun inflateRecords(records: List<ByteArray>): List<ByteArray> {
|
|
21
|
+
val inflater = Inflater()
|
|
22
|
+
val out = mutableListOf<ByteArray>()
|
|
23
|
+
for (record in records) {
|
|
24
|
+
val flags = record[0].toInt() and 0xFF
|
|
25
|
+
val length = (record[1].toInt() and 0xFF) or ((record[2].toInt() and 0xFF) shl 8)
|
|
26
|
+
assertEquals(record.size - 5, length)
|
|
27
|
+
val body = record.copyOfRange(5, record.size)
|
|
28
|
+
if (flags and G2CfwTransport.FLAG_COMPRESSED == 0) {
|
|
29
|
+
out.add(body)
|
|
30
|
+
continue
|
|
31
|
+
}
|
|
32
|
+
// Every compressed record ends in a complete SYNC_FLUSH marker.
|
|
33
|
+
assertEquals(listOf(0x00, 0x00, 0xFF, 0xFF), body.takeLast(4).map { it.toInt() and 0xFF })
|
|
34
|
+
// As the firmware does: a RESET_CONTEXT record starts a fresh inflate stream.
|
|
35
|
+
if (flags and G2CfwTransport.FLAG_RESET_CONTEXT != 0) inflater.reset()
|
|
36
|
+
inflater.setInput(body)
|
|
37
|
+
|
|
38
|
+
val buffer = ByteArray(0x10000)
|
|
39
|
+
val produced = inflater.inflate(buffer)
|
|
40
|
+
assertTrue(inflater.needsInput())
|
|
41
|
+
out.add(buffer.copyOf(produced))
|
|
42
|
+
}
|
|
43
|
+
inflater.end()
|
|
44
|
+
return out
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
@Test
|
|
48
|
+
fun recordsShareOneContextAndCarryThePlainCrc() {
|
|
49
|
+
val encoder = G2CfwTransport.Encoder()
|
|
50
|
+
val first = byteArrayOf(6) + ByteArray(5000)
|
|
51
|
+
val second = byteArrayOf(3, 1, 2, 3, 4, 5, 0) + ByteArray(300) { 0x1F }
|
|
52
|
+
val r1 = encoder.record(first, 3)!!
|
|
53
|
+
val r2 = encoder.record(second, 3)!!
|
|
54
|
+
// First record resets the context (flag 8); the second continues it.
|
|
55
|
+
assertEquals(3 or 4 or 8, r1[0].toInt() and 0xFF)
|
|
56
|
+
assertEquals(3 or 4, r2[0].toInt() and 0xFF)
|
|
57
|
+
assertEquals(G2CfwTransport.crc16(first), (r1[3].toInt() and 0xFF) or ((r1[4].toInt() and 0xFF) shl 8))
|
|
58
|
+
assertEquals(G2CfwTransport.crc16(second), (r2[3].toInt() and 0xFF) or ((r2[4].toInt() and 0xFF) shl 8))
|
|
59
|
+
assertTrue(r1.size < 100) // 5000 zero bytes deflate to a few
|
|
60
|
+
val decoded = inflateRecords(listOf(r1, r2))
|
|
61
|
+
assertArrayEquals(first, decoded[0])
|
|
62
|
+
assertArrayEquals(second, decoded[1])
|
|
63
|
+
// After a NACK/timeout the context restarts and the next record says so.
|
|
64
|
+
encoder.resetContext()
|
|
65
|
+
val r3 = encoder.record(second, 1)!!
|
|
66
|
+
assertEquals(1 or 4 or 8, r3[0].toInt() and 0xFF)
|
|
67
|
+
assertArrayEquals(second, inflateRecords(listOf(r3))[0])
|
|
68
|
+
assertNull(encoder.record(ByteArray(0x10000), 3))
|
|
69
|
+
assertNull(encoder.record(byteArrayOf(7, 0xFF.toByte()), 4))
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
@Test
|
|
73
|
+
fun lensTargetedRecordsAreSelfContainedSoTheSkippingLensStaysInSync() {
|
|
74
|
+
val encoder = G2CfwTransport.Encoder()
|
|
75
|
+
val shared = byteArrayOf(38, 0, 0, 0, 0, 1, 0) + ByteArray(400) { 0x2A }
|
|
76
|
+
val leftOnly = byteArrayOf(38, 0, 0, 1, 0, 1, 0) + ByteArray(400) { 0x2B }
|
|
77
|
+
val rightOnly = byteArrayOf(38, 0, 0, 2, 0, 1, 0) + ByteArray(400) { 0x2C }
|
|
78
|
+
val r1 = encoder.record(shared, 3)!!
|
|
79
|
+
val r2 = encoder.record(leftOnly, 1)!!
|
|
80
|
+
val r3 = encoder.record(rightOnly, 2)!!
|
|
81
|
+
val r4 = encoder.record(shared, 3)!!
|
|
82
|
+
val r5 = encoder.record(shared, 3)!!
|
|
83
|
+
assertEquals("a left-only record starts a fresh context", 1 or 4 or 8, r2[0].toInt() and 0xFF)
|
|
84
|
+
assertEquals(2 or 4 or 8, r3[0].toInt() and 0xFF)
|
|
85
|
+
assertEquals("and the record after it starts fresh again", 3 or 4 or 8, r4[0].toInt() and 0xFF)
|
|
86
|
+
assertEquals("then the shared context resumes", 3 or 4, r5[0].toInt() and 0xFF)
|
|
87
|
+
// Each lens inflates exactly the records addressed to it.
|
|
88
|
+
val left = inflateRecords(listOf(r1, r2, r4, r5))
|
|
89
|
+
assertArrayEquals(leftOnly, left[1]); assertArrayEquals(shared, left[3])
|
|
90
|
+
val right = inflateRecords(listOf(r1, r3, r4, r5))
|
|
91
|
+
assertArrayEquals(rightOnly, right[1]); assertArrayEquals(shared, right[3])
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
|
|
95
|
+
@Test
|
|
96
|
+
fun packetsMatchTheProbeFraming() {
|
|
97
|
+
val stream = ByteArray(500) { (it and 0xFF).toByte() }
|
|
98
|
+
val packets = G2CfwTransport.packets(stream, 254, 3, chunkBytes = 200)
|
|
99
|
+
assertEquals(3, packets.size)
|
|
100
|
+
// [aa 21 seq len+2 1 1 f0 0][options][chunk][crc lo][crc hi]
|
|
101
|
+
assertEquals(listOf(0xAA, 0x21, 254, 203, 1, 1, 0xF0, 0, 3 or 0x80), packets[0].take(9).map { it.toInt() and 0xFF })
|
|
102
|
+
assertEquals(listOf(0xAA, 0x21, 255, 203, 1, 1, 0xF0, 0, 3), packets[1].take(9).map { it.toInt() and 0xFF })
|
|
103
|
+
assertEquals(listOf(0xAA, 0x21, 0, 103, 1, 1, 0xF0, 0, 3 or 0x40), packets[2].take(9).map { it.toInt() and 0xFF })
|
|
104
|
+
assertEquals(8 + 1 + 100 + 2, packets[2].size)
|
|
105
|
+
for (packet in packets) {
|
|
106
|
+
val body = packet.copyOfRange(8, packet.size - 2)
|
|
107
|
+
val crc = G2CfwTransport.crc16(body)
|
|
108
|
+
assertEquals(crc and 0xFF, packet[packet.size - 2].toInt() and 0xFF)
|
|
109
|
+
assertEquals(crc ushr 8, packet[packet.size - 1].toInt() and 0xFF)
|
|
110
|
+
assertEquals(packet.size - 8, packet[3].toInt() and 0xFF)
|
|
111
|
+
}
|
|
112
|
+
val joined = packets.flatMap { it.copyOfRange(9, it.size - 2).toList() }.toByteArray()
|
|
113
|
+
assertArrayEquals(stream, joined)
|
|
114
|
+
// An empty stream is one RESET|END packet.
|
|
115
|
+
val empty = G2CfwTransport.packets(ByteArray(0), 7, 1)
|
|
116
|
+
assertEquals(1, empty.size)
|
|
117
|
+
assertEquals(listOf(0xAA, 0x21, 7, 3, 1, 1, 0xF0, 0, 1 or 0x80 or 0x40), empty[0].take(9).map { it.toInt() and 0xFF })
|
|
118
|
+
assertTrue(G2CfwTransport.MAX_CHUNK_BYTES <= 252)
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
@Test
|
|
122
|
+
fun ackParsingIncludesHistoryAndRejectsMalformedReplies() {
|
|
123
|
+
val body = byteArrayOf(1, 7, 0, 0, 2, 0x34, 0x12, 0xCD.toByte(), 0xAB.toByte(), 6, 1, 0, 0x10, 0, 0x22, 0x11)
|
|
124
|
+
val ack = G2CfwTransport.parseAck(body)!!
|
|
125
|
+
assertTrue(ack.success)
|
|
126
|
+
assertEquals(2, ack.lens)
|
|
127
|
+
assertEquals(G2CfwTransport.AckEntry(7, 0, 0x1234, 0xABCD), ack.current)
|
|
128
|
+
assertEquals(2, ack.entries.size)
|
|
129
|
+
assertEquals(G2CfwTransport.AckEntry(6, 1, 0x10, 0x1122), ack.entries[1])
|
|
130
|
+
val nack = G2CfwTransport.parseAck(byteArrayOf(3, 7, 0, 0, 1, 0, 0, 0, 0))!!
|
|
131
|
+
assertFalse(nack.success)
|
|
132
|
+
assertNull(G2CfwTransport.parseAck(byteArrayOf(3, 7, 0, 0, 1, 0, 0, 0, 0, 6, 1, 0, 0, 0, 0, 0))) // NACK has no history
|
|
133
|
+
assertNull(G2CfwTransport.parseAck(byteArrayOf(2, 7, 0, 0, 1, 0, 0, 0, 0)))
|
|
134
|
+
assertNull(G2CfwTransport.parseAck(byteArrayOf(1, 7, 0, 0, 3, 0, 0, 0, 0)))
|
|
135
|
+
assertNull(G2CfwTransport.parseAck(byteArrayOf(1, 7, 0, 0)))
|
|
136
|
+
|
|
137
|
+
// Notify frame as the glasses send it: [aa 12 seq len 1 1 f0 0][body][crc].
|
|
138
|
+
val crc = G2CfwTransport.crc16(body)
|
|
139
|
+
val frame = byteArrayOf(0xAA.toByte(), 0x12, 9, (body.size + 2).toByte(), 1, 1, 0xF0.toByte(), 0) + body +
|
|
140
|
+
byteArrayOf((crc and 0xFF).toByte(), (crc ushr 8).toByte())
|
|
141
|
+
assertArrayEquals(body, G2CfwTransport.replyBody(frame))
|
|
142
|
+
val corrupt = frame.copyOf().also { it[10] = (it[10].toInt() xor 1).toByte() }
|
|
143
|
+
assertNull(G2CfwTransport.replyBody(corrupt))
|
|
144
|
+
val otherService = frame.copyOf().also { it[6] = 0xE0.toByte() }
|
|
145
|
+
assertNull(G2CfwTransport.replyBody(otherService))
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
@Test
|
|
149
|
+
fun ackBoxCompletesWhenBothLensesAckAndFailsOnNack() = runBlocking {
|
|
150
|
+
val box = G2CfwAckBox()
|
|
151
|
+
val left = G2CfwTransport.parseAck(byteArrayOf(1, 5, 0, 0, 1, 2, 0, 0, 0))!!
|
|
152
|
+
val right = G2CfwTransport.parseAck(byteArrayOf(1, 5, 0, 0, 2, 2, 0, 0, 0))!!
|
|
153
|
+
val first = box.arm(5, 3)
|
|
154
|
+
assertFalse(box.resolve(left)) // one lens is not enough
|
|
155
|
+
assertFalse(box.resolve(left)) // duplicate is harmless
|
|
156
|
+
assertTrue(box.resolve(right))
|
|
157
|
+
assertEquals(true, withTimeoutOrNull(1000) { first.await() })
|
|
158
|
+
assertEquals("ack", box.lastOutcome)
|
|
159
|
+
assertTrue(box.lastSuccessMs > 0)
|
|
160
|
+
|
|
161
|
+
val nack = G2CfwTransport.parseAck(byteArrayOf(3, 6, 0, 0, 2, 0, 0, 0, 0))!!
|
|
162
|
+
val second = box.arm(6, 3)
|
|
163
|
+
assertFalse(box.resolve(left)) // wrong stream
|
|
164
|
+
assertTrue(box.resolve(nack))
|
|
165
|
+
assertEquals(false, withTimeoutOrNull(1000) { second.await() })
|
|
166
|
+
assertEquals("nack", box.lastOutcome)
|
|
167
|
+
|
|
168
|
+
val third = box.arm(7, 1)
|
|
169
|
+
assertNull(withTimeoutOrNull(50) { third.await() })
|
|
170
|
+
box.disarm(third, "timeout")
|
|
171
|
+
assertEquals("timeout", box.lastOutcome)
|
|
172
|
+
assertFalse(box.resolve(G2CfwTransport.parseAck(byteArrayOf(1, 7, 0, 0, 1, 0, 0, 0, 0))!!))
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
@Test
|
|
176
|
+
fun expiredQueueGuardsStayExpiredAfterStreamIdReuse() {
|
|
177
|
+
val box = G2CfwAckBox()
|
|
178
|
+
val expired = box.arm(7, 3)
|
|
179
|
+
val oldGuard = { box.isPending(expired) }
|
|
180
|
+
assertTrue(oldGuard())
|
|
181
|
+
box.disarm(expired, "timeout")
|
|
182
|
+
assertFalse(oldGuard())
|
|
183
|
+
val replacement = box.arm(7, 3)
|
|
184
|
+
assertFalse(oldGuard())
|
|
185
|
+
assertTrue(box.isPending(replacement))
|
|
186
|
+
// Cleanup from an old coroutine cannot disarm a newer generation.
|
|
187
|
+
box.disarm(expired, "cancelled")
|
|
188
|
+
assertTrue(box.isPending(replacement))
|
|
189
|
+
box.disarm(replacement, "cancelled")
|
|
190
|
+
assertFalse(box.isPending(replacement))
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
@Test
|
|
194
|
+
fun queueGuardExpiresOnCancellationNackAndFullAckButNotPartialAck() {
|
|
195
|
+
val box = G2CfwAckBox()
|
|
196
|
+
val cancelled = box.arm(7, 3)
|
|
197
|
+
cancelled.cancel()
|
|
198
|
+
assertFalse(box.isPending(cancelled))
|
|
199
|
+
val pending = box.arm(7, 3)
|
|
200
|
+
val entry = listOf(G2CfwTransport.AckEntry(7, 0, 0, 0))
|
|
201
|
+
assertFalse(box.resolve(G2CfwTransport.Ack(true, 1, entry)))
|
|
202
|
+
assertTrue(box.isPending(pending))
|
|
203
|
+
assertTrue(box.resolve(G2CfwTransport.Ack(true, 2, entry)))
|
|
204
|
+
assertFalse(box.isPending(pending))
|
|
205
|
+
val rejected = box.arm(7, 3)
|
|
206
|
+
assertTrue(box.resolve(G2CfwTransport.Ack(false, 1, entry)))
|
|
207
|
+
assertFalse(box.isPending(rejected))
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
@Test
|
|
211
|
+
fun cacheRepliesParseAndStampFillsEpochAndRequest() {
|
|
212
|
+
val body = byteArrayOf(5, 9, 0, 0, 2, 38, 7, 0, 2, 0x34, 0x12, 5, 0, 0, 0, 2, 1, 1, 0, 0, 0, 0, 1, 0, 0)
|
|
213
|
+
val reply = G2CfwTransport.parseCacheReply(body)
|
|
214
|
+
assertNotNull(reply)
|
|
215
|
+
reply!!
|
|
216
|
+
assertEquals(9, reply.streamId)
|
|
217
|
+
assertEquals(2, reply.lens)
|
|
218
|
+
assertEquals(38, reply.mode)
|
|
219
|
+
assertEquals(7, reply.request)
|
|
220
|
+
assertEquals(G2CfwTransport.CacheReply.MISSING, reply.status)
|
|
221
|
+
assertEquals(0x1234, reply.epoch)
|
|
222
|
+
assertEquals(5L, reply.revision)
|
|
223
|
+
assertEquals(listOf(1, 256), reply.missingIds)
|
|
224
|
+
assertTrue(reply.summary.startsWith("missing"))
|
|
225
|
+
assertNull(G2CfwTransport.parseCacheReply(byteArrayOf(1, 9, 0, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0)))
|
|
226
|
+
assertNull(G2CfwTransport.parseAck(body))
|
|
227
|
+
|
|
228
|
+
val show = byteArrayOf(38, 0, 0, 0, 0, 1, 0, 0, 0, 0)
|
|
229
|
+
assertTrue(G2CfwTransport.isCacheMessage(show))
|
|
230
|
+
assertFalse(G2CfwTransport.isCacheMessage(byteArrayOf(36, 0, 0, 0, 0)))
|
|
231
|
+
assertEquals(listOf(38, 0x34, 0x12, 2, 1), G2CfwTransport.stamp(show, 0x1234, 0x0102).take(5).map { it.toInt() and 0xFF })
|
|
232
|
+
assertEquals(listOf(38, 1, 0, 9, 0), G2CfwTransport.stamp(byteArrayOf(38, 0, 0, 9, 0, 1, 0, 0, 0, 0), 1, 7).take(5).map { it.toInt() and 0xFF })
|
|
233
|
+
assertEquals(listOf(40, 1, 0, 0, 0), G2CfwTransport.stamp(byteArrayOf(40, 0, 0, 0, 0, 0, 0, 0, 0), 1, 7).take(5).map { it.toInt() and 0xFF })
|
|
234
|
+
|
|
235
|
+
val box = G2CfwAckBox()
|
|
236
|
+
val deferred = box.arm(9, G2CfwTransport.LENS_BOTH)
|
|
237
|
+
box.record(reply)
|
|
238
|
+
assertFalse(box.resolve(G2CfwTransport.Ack(true, 2, listOf(G2CfwTransport.AckEntry(9, 0, 1, 0)))))
|
|
239
|
+
assertTrue(box.resolve(G2CfwTransport.Ack(true, 1, listOf(G2CfwTransport.AckEntry(9, 0, 1, 0)))))
|
|
240
|
+
assertTrue(deferred.isCompleted)
|
|
241
|
+
assertEquals(reply, box.cacheReplies()[2])
|
|
242
|
+
assertNull(box.cacheReplies()[1])
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
@Test
|
|
246
|
+
fun ackTimeoutScalesWithStreamSize() {
|
|
247
|
+
assertEquals(3500L, G2CfwTransport.ackTimeoutMs(0))
|
|
248
|
+
assertEquals(3500L + 65535, G2CfwTransport.ackTimeoutMs(65535))
|
|
249
|
+
// A 4.5 KB upload chunk acked in ~4.1 s on GLASSLYCFW/39; the deadline must clear it.
|
|
250
|
+
assertTrue(G2CfwTransport.ackTimeoutMs(4500) > 6000L)
|
|
251
|
+
}
|
|
252
|
+
}
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
package com.glassly.bluetoothsdk.sgcs
|
|
2
|
+
|
|
3
|
+
import org.junit.Assert.*
|
|
4
|
+
import org.junit.Test
|
|
5
|
+
|
|
6
|
+
class G2DisplayDiagnosticsTest {
|
|
7
|
+
@Test fun samplesAreThrottledResetAndLeaveOutEmptyValues() {
|
|
8
|
+
val diagnostics = G2DisplayDiagnostics(0)
|
|
9
|
+
diagnostics.count("framesIn")
|
|
10
|
+
diagnostics.count("txBytes", 100)
|
|
11
|
+
diagnostics.timing("transferMs", 10)
|
|
12
|
+
diagnostics.timing("transferMs", 31)
|
|
13
|
+
diagnostics.timing("encodeMs", 0)
|
|
14
|
+
assertNull(diagnostics.sample(900, 9000, emptyMap()))
|
|
15
|
+
val first = diagnostics.sample(1000, 10000, mapOf("app" to "system.gesture-dots", "payloadQueue" to 0, "page" to false))!!
|
|
16
|
+
assertEquals(100, first["txBytes"])
|
|
17
|
+
assertEquals(20.5, first["transferMsAvg"])
|
|
18
|
+
assertEquals(31L, first["transferMsMax"])
|
|
19
|
+
assertNull(first["transferMsTotal"])
|
|
20
|
+
assertNull(first["encodeMsAvg"])
|
|
21
|
+
assertEquals(10000L, first["t"])
|
|
22
|
+
assertEquals("system.gesture-dots", first["app"])
|
|
23
|
+
assertNull(first["payloadQueue"])
|
|
24
|
+
assertNull(first["page"])
|
|
25
|
+
assertNull(first["timeouts"])
|
|
26
|
+
assertEquals(8, (first["run"] as String).length)
|
|
27
|
+
diagnostics.count("framesIn")
|
|
28
|
+
val second = diagnostics.sample(2000, 11000, emptyMap())!!
|
|
29
|
+
assertNull(second["txBytes"])
|
|
30
|
+
assertNull(second["transferMsAvg"])
|
|
31
|
+
assertEquals(first["run"], second["run"])
|
|
32
|
+
assertEquals(2, second["id"])
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
@Test fun idleWindowsAreSkippedUnlessBusyOrLate() {
|
|
36
|
+
val diagnostics = G2DisplayDiagnostics(0)
|
|
37
|
+
diagnostics.count("imuReceived")
|
|
38
|
+
diagnostics.count("txBytes", 32)
|
|
39
|
+
diagnostics.timing("notifyDispatchMs", 1)
|
|
40
|
+
assertNull(diagnostics.sample(1000, 10000, emptyMap()))
|
|
41
|
+
assertNotNull(diagnostics.sample(2000, 11000, mapOf("payloadQueue" to 2), busy = true))
|
|
42
|
+
assertNull(diagnostics.sample(3000, 12000, emptyMap()))
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
@Test fun concurrentWriterCountersAreNotLostAndLongWindowsRemainVisible() {
|
|
46
|
+
val diagnostics = G2DisplayDiagnostics(0)
|
|
47
|
+
val workers = List(4) { Thread { repeat(1000) { diagnostics.count("txPackets") } } }
|
|
48
|
+
workers.forEach { it.start() }
|
|
49
|
+
workers.forEach { it.join() }
|
|
50
|
+
val sample = diagnostics.sample(25000, 100000, emptyMap())!!
|
|
51
|
+
assertEquals(4000, sample["txPackets"])
|
|
52
|
+
assertEquals(25000L, sample["windowMs"])
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
@Test fun sessionFactsAreLoggedOnChangeAndOnceAMinute() {
|
|
56
|
+
val diagnostics = G2DisplayDiagnostics(0)
|
|
57
|
+
val facts = mapOf("platform" to "android", "cfw" to "GLASSLYCFW/39")
|
|
58
|
+
assertEquals("session", diagnostics.session(1000, 10000, facts)!!["type"])
|
|
59
|
+
assertNull(diagnostics.session(30_000, 39_000, facts))
|
|
60
|
+
assertNotNull(diagnostics.session(31_000, 40_000, facts + ("cfw" to "stock")))
|
|
61
|
+
assertNull(diagnostics.session(90_000, 99_000, facts + ("cfw" to "stock")))
|
|
62
|
+
assertNotNull(diagnostics.session(91_000, 100_000, facts + ("cfw" to "stock")))
|
|
63
|
+
}
|
|
64
|
+
}
|