Starter display: boundary v0 client, readout, mark button, CI
CI / Analyze & Test (push) Successful in 12s
CI / Debug APK (push) Successful in 57s

Connects to svc-gw over one WebSocket, heartbeats, treats timeout_ms of
silence as a dead link, and retries commands under the same id until acked.
Shows link state, signals with stale/null/missing told apart, the raw stream,
a mock banner on the surface, and a Mark button that stamps the recording.
Android: keeps the screen on and binds the process to the USB NCM network
when the gateway is on it. CI mirrors android-stuff's host-mode runner setup.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DDwfd6Ki8UfG4hK6W5YpJz
This commit is contained in:
2026-10-09 20:23:00 +09:00
parent 62c6a79f89
commit 50081d5417
10 changed files with 770 additions and 132 deletions
+5 -1
View File
@@ -1,6 +1,10 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
<uses-permission android:name="android.permission.INTERNET"/>
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/>
<!-- requestNetwork for the USB (NCM) Ethernet link to the Orin -->
<uses-permission android:name="android.permission.CHANGE_NETWORK_STATE"/>
<application
android:label="display"
android:label="Yukart"
android:name="${applicationName}"
android:icon="@mipmap/ic_launcher">
<activity
@@ -1,5 +1,83 @@
package dev.yukart.display
import android.content.Context
import android.net.ConnectivityManager
import android.net.Network
import android.net.NetworkCapabilities
import android.net.NetworkRequest
import android.os.Bundle
import android.view.WindowManager
import io.flutter.embedding.android.FlutterActivity
import io.flutter.embedding.engine.FlutterEngine
import io.flutter.plugin.common.MethodChannel
class MainActivity : FlutterActivity()
/**
* Two things Dart cannot do on its own:
*
* - Keep the screen on. An idle tablet suspends about a minute after screen-off and
* then answers nothing, while the Orin still sees the USB link up.
* - Route over the USB link. It has no internet, so Android never makes it the
* default network while WiFi or cellular is up, and ordinary sockets go elsewhere.
* When asked, the whole process is bound to the Ethernet (NCM) network while it
* exists, and unbound when it goes.
*/
class MainActivity : FlutterActivity() {
private lateinit var channel: MethodChannel
private lateinit var cm: ConnectivityManager
private var ethernet: Network? = null
private var wantBinding = false
private val callback = object : ConnectivityManager.NetworkCallback() {
override fun onAvailable(network: Network) {
ethernet = network
apply()
}
override fun onLost(network: Network) {
if (ethernet == network) {
ethernet = null
apply()
}
}
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
window.addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON)
}
override fun configureFlutterEngine(flutterEngine: FlutterEngine) {
super.configureFlutterEngine(flutterEngine)
cm = getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager
channel = MethodChannel(flutterEngine.dartExecutor.binaryMessenger, "yukart/net")
channel.setMethodCallHandler { call, result ->
when (call.method) {
"bindEthernet" -> {
wantBinding = call.arguments as? Boolean ?: false
apply()
result.success(ethernet != null)
}
else -> result.notImplemented()
}
}
// NET_CAPABILITY_INTERNET is in the default request; the USB link lacks it.
val request = NetworkRequest.Builder()
.addTransportType(NetworkCapabilities.TRANSPORT_ETHERNET)
.removeCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET)
.build()
cm.requestNetwork(request, callback)
}
private fun apply() {
val target = if (wantBinding) ethernet else null
cm.bindProcessToNetwork(target)
val up = ethernet != null
runOnUiThread { channel.invokeMethod("ethernet", mapOf("up" to up, "bound" to (target != null))) }
}
override fun onDestroy() {
runCatching { cm.unregisterNetworkCallback(callback) }
cm.bindProcessToNetwork(null)
super.onDestroy()
}
}