@@ -30,6 +30,7 @@ import androidx.compose.ui.platform.LocalContext
3030import coil.ImageLoader
3131import coil.disk.DiskCache
3232import coil.memory.MemoryCache
33+ import coil.intercept.Interceptor
3334import coil.request.CachePolicy
3435import app.gamenative.events.AndroidEvent
3536import app.gamenative.service.SteamService
@@ -48,15 +49,50 @@ import com.winlator.core.AppUtils
4849import com.winlator.inputcontrols.ControllerManager
4950import dagger.hilt.android.AndroidEntryPoint
5051import kotlinx.coroutines.launch
52+ import java.util.Collections
5153import java.util.EnumSet
54+ import java.util.concurrent.atomic.AtomicBoolean
55+ import android.net.ConnectivityManager
56+ import android.net.Network
57+ import android.net.NetworkCapabilities
58+ import android.net.NetworkRequest
5259import kotlin.math.abs
5360import okio.Path.Companion.toOkioPath
5461import timber.log.Timber
5562
5663@AndroidEntryPoint
5764class MainActivity : ComponentActivity () {
5865
66+ // ignore VPN and mesh transports — they don't reliably indicate internet
67+ private val networkCallback = object : ConnectivityManager .NetworkCallback () {
68+ private val validated = Collections .synchronizedSet(mutableSetOf<Network >())
69+
70+ private fun skip (caps : NetworkCapabilities ) =
71+ caps.hasTransport(NetworkCapabilities .TRANSPORT_VPN ) ||
72+ caps.hasTransport(NetworkCapabilities .TRANSPORT_WIFI_AWARE ) ||
73+ caps.hasTransport(NetworkCapabilities .TRANSPORT_LOWPAN )
74+
75+ override fun onCapabilitiesChanged (network : Network , caps : NetworkCapabilities ) {
76+ if (skip(caps)) return
77+ if (caps.hasCapability(NetworkCapabilities .NET_CAPABILITY_VALIDATED )) {
78+ validated.add(network)
79+ } else {
80+ validated.remove(network)
81+ }
82+ _hasInternet .set(validated.isNotEmpty())
83+ }
84+
85+ override fun onLost (network : Network ) {
86+ validated.remove(network)
87+ _hasInternet .set(validated.isNotEmpty())
88+ }
89+ }
90+
5991 companion object {
92+ // updated by NetworkCallback, read by Coil interceptor
93+ private val _hasInternet = AtomicBoolean (false )
94+ val hasInternet: Boolean get() = _hasInternet .get()
95+
6096 private var totalIndex = 0
6197
6298 private var currentOrientationChangeValue: Int = 0
@@ -141,6 +177,15 @@ class MainActivity : ComponentActivity() {
141177
142178 handleLaunchIntent(intent)
143179
180+ // track real network state (callback filters out VPN/mesh transports)
181+ val cm = getSystemService(Context .CONNECTIVITY_SERVICE ) as ConnectivityManager
182+ cm.registerNetworkCallback(
183+ NetworkRequest .Builder ()
184+ .addCapability(NetworkCapabilities .NET_CAPABILITY_INTERNET )
185+ .build(),
186+ networkCallback,
187+ )
188+
144189 // Prevent device from sleeping while app is open
145190 AppUtils .keepScreenOn(this )
146191
@@ -184,10 +229,20 @@ class MainActivity : ComponentActivity() {
184229 .diskCachePolicy(CachePolicy .ENABLED )
185230 .diskCache(diskCache)
186231 .components {
232+ // serve cached images when device has no internet
233+ add(Interceptor { chain ->
234+ val request = if (! hasInternet) {
235+ chain.request.newBuilder()
236+ .networkCachePolicy(CachePolicy .DISABLED )
237+ .build()
238+ } else {
239+ chain.request
240+ }
241+ chain.proceed(request)
242+ })
187243 add(IconDecoder .Factory ())
188244 add(AnimatedPngDecoder .Factory ())
189245 }
190- // .logger(logger)
191246 .build()
192247 }
193248
@@ -238,6 +293,9 @@ class MainActivity : ComponentActivity() {
238293 override fun onDestroy () {
239294 super .onDestroy()
240295
296+ val cm = getSystemService(Context .CONNECTIVITY_SERVICE ) as ConnectivityManager
297+ cm.unregisterNetworkCallback(networkCallback)
298+
241299 PluviaApp .events.emit(AndroidEvent .ActivityDestroyed )
242300
243301 PluviaApp .events.off<AndroidEvent .SetSystemUIVisibility , Unit >(onSetSystemUi)
0 commit comments