Auto dark/bright follows the sun at the tablet's own location
/ crosscheck (push) Successful in 1s
CI / Analyze & Test (push) Successful in 22s
CI / Debug APK (push) Successful in 39s

Without a light sensor an app may read (the Redmi Pad SE has none), auto is dark while
the sun is more than 4° below the horizon (lib/theme/sun.dart), at Android's coarse
location, polled and kept across restarts. The tablet's own, not the Orin's GNSS: the
tablet has its own GNSS and cellular. Android's dark theme is the last fallback.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
2026-10-15 22:33:43 +09:00
parent 317de3425a
commit 31a844bb94
9 changed files with 164 additions and 19 deletions
+2
View File
@@ -3,6 +3,8 @@
<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"/>
<!-- Where the tablet is, roughly: for the sun (auto dark/bright, lib/theme/sun.dart). -->
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION"/>
<application
android:label="Yukart"
android:name="${applicationName}"
@@ -1,15 +1,19 @@
package dev.yukart.display
import android.Manifest
import android.content.Context
import android.content.Intent
import android.content.pm.PackageManager
import android.hardware.Sensor
import android.hardware.SensorEvent
import android.hardware.SensorEventListener
import android.hardware.SensorManager
import android.location.LocationManager
import android.net.ConnectivityManager
import android.net.Network
import android.net.NetworkCapabilities
import android.net.NetworkRequest
import android.os.Build
import android.os.Bundle
import android.view.WindowManager
import io.flutter.embedding.android.FlutterActivity
@@ -29,6 +33,7 @@ import io.flutter.plugin.common.MethodChannel
*
* Also the light sensor, for auto dark/bright (lib/theme/light.dart): lux on `yukart/light`
* while Dart listens, where the device lets apps read one (the Redmi Pad SE does not).
* And, for the sun instead, the tablet's own rough location on `yukart/location`.
*
* Also a development hook: `am start -n dev.yukart.display/.MainActivity --es linktest [dl:|bi:]<label>[@<host>]`
* runs the link test (lib/linktest.dart) without touching the screen.
@@ -39,6 +44,7 @@ class MainActivity : FlutterActivity() {
private var ethernet: Network? = null
private var wantBinding = false
private var pendingLinkTest: String? = null
private var askedLocation = false
private val callback = object : ConnectivityManager.NetworkCallback() {
override fun onAvailable(network: Network) {
@@ -90,10 +96,38 @@ class MainActivity : FlutterActivity() {
.build()
cm.requestNetwork(request, callback)
MethodChannel(flutterEngine.dartExecutor.binaryMessenger, "yukart/location").setMethodCallHandler { call, result ->
if (call.method == "last") result.success(lastLocation()) else result.notImplemented()
}
EventChannel(flutterEngine.dartExecutor.binaryMessenger, "yukart/light")
.setStreamHandler(LightStream(getSystemService(Context.SENSOR_SERVICE) as SensorManager))
}
/**
* [lat, lon] of the newest location Android has (other apps' fixes included), or null.
* Asks for coarse location the first time; with none known, asks for one, which the
* next call sees.
*/
private fun lastLocation(): List<Double>? {
if (checkSelfPermission(Manifest.permission.ACCESS_COARSE_LOCATION) != PackageManager.PERMISSION_GRANTED) {
if (!askedLocation) {
askedLocation = true
requestPermissions(arrayOf(Manifest.permission.ACCESS_COARSE_LOCATION), 1)
}
return null
}
val lm = getSystemService(Context.LOCATION_SERVICE) as LocationManager
val enabled = lm.getProviders(true)
val best = enabled.mapNotNull { runCatching { lm.getLastKnownLocation(it) }.getOrNull() }.maxByOrNull { it.time }
if (best == null && Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
listOf("fused", LocationManager.NETWORK_PROVIDER, LocationManager.GPS_PROVIDER)
.firstOrNull { it in enabled }
?.let { runCatching { lm.getCurrentLocation(it, null, mainExecutor) {} } }
}
return best?.let { listOf(it.latitude, it.longitude) }
}
/** Lux from the default light sensor while listened to; an error at once without one, or when refused. */
private class LightStream(private val sm: SensorManager) : EventChannel.StreamHandler {
private var listener: SensorEventListener? = null