147 lines
6.1 KiB
Kotlin
147 lines
6.1 KiB
Kotlin
package com.tv.browser
|
|
|
|
import android.content.Context
|
|
import android.net.ConnectivityManager
|
|
import android.net.NetworkCapabilities
|
|
import android.os.Build
|
|
import java.net.HttpURLConnection
|
|
import java.net.Inet4Address
|
|
import java.net.NetworkInterface
|
|
import java.net.URL
|
|
import java.util.Calendar
|
|
import kotlin.math.abs
|
|
|
|
/**
|
|
* 检查网络连接状态、系统验证结果、互联网实际可达性与系统时钟偏差。
|
|
*/
|
|
object NetworkChecker {
|
|
|
|
private const val INTERNET_CHECK_URL = "https://www.baidu.com"
|
|
private const val INTERNET_CHECK_TIMEOUT_MS = 8000
|
|
/** 允许的设备时钟与服务器 Date 响应头最大偏差(7 天),超过则判定为电视时钟未同步 */
|
|
private const val MAX_CLOCK_SKEW_MS = 7L * 24 * 3600 * 1000L
|
|
/** 最低合理年份基线(仅在尚未收到任何 HTTP Date 响应头时兜底) */
|
|
private const val MIN_VALID_YEAR = 2026
|
|
|
|
@Volatile
|
|
private var lastObservedSkewMs: Long? = null
|
|
|
|
/** 根据服务器返回的 Date 头毫秒时间戳更新时钟偏差记录 */
|
|
fun updateServerDate(serverDateMs: Long) {
|
|
if (serverDateMs > 0L) {
|
|
lastObservedSkewMs = abs(System.currentTimeMillis() - serverDateMs)
|
|
}
|
|
}
|
|
|
|
/**
|
|
* 判定电视系统时钟是否明显异常:
|
|
* 优先对比最近一次 HTTPS/HTTP 响应 Date 头与本地时间的偏差(不随年份推移腐烂);
|
|
* 若尚未发起过网络请求,则用最低合理年份兜底(如电视断电重置回 1970/2015 年)。
|
|
*/
|
|
fun isSystemClockAbnormal(): Boolean {
|
|
val skew = lastObservedSkewMs
|
|
if (skew != null) {
|
|
return skew > MAX_CLOCK_SKEW_MS
|
|
}
|
|
return Calendar.getInstance().get(Calendar.YEAR) < MIN_VALID_YEAR
|
|
}
|
|
|
|
/**
|
|
* 返回链路状态文案:已连接时区分「系统已验证互联网」与「未验证」。
|
|
*/
|
|
fun getStatusMessage(context: Context): String {
|
|
val cm = context.getSystemService(Context.CONNECTIVITY_SERVICE) as? ConnectivityManager
|
|
?: return context.getString(R.string.network_disconnected)
|
|
|
|
return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
|
|
val network = cm.activeNetwork ?: return context.getString(R.string.network_disconnected)
|
|
val caps = cm.getNetworkCapabilities(network) ?: return context.getString(R.string.network_disconnected)
|
|
val typeName = when {
|
|
caps.hasTransport(NetworkCapabilities.TRANSPORT_WIFI) ->
|
|
context.getString(R.string.network_type_wifi)
|
|
caps.hasTransport(NetworkCapabilities.TRANSPORT_ETHERNET) ->
|
|
context.getString(R.string.network_type_ethernet)
|
|
else -> context.getString(R.string.network_type_unknown)
|
|
}
|
|
val validated = Build.VERSION.SDK_INT >= Build.VERSION_CODES.N &&
|
|
caps.hasCapability(NetworkCapabilities.NET_CAPABILITY_VALIDATED)
|
|
if (validated) {
|
|
context.getString(R.string.network_connected, typeName)
|
|
} else {
|
|
context.getString(R.string.network_connected_not_validated, typeName)
|
|
}
|
|
} else {
|
|
@Suppress("DEPRECATION")
|
|
val info = cm.activeNetworkInfo
|
|
if (info?.isConnected == true) {
|
|
val typeName = when (info.type) {
|
|
ConnectivityManager.TYPE_WIFI -> context.getString(R.string.network_type_wifi)
|
|
ConnectivityManager.TYPE_ETHERNET -> context.getString(R.string.network_type_ethernet)
|
|
else -> context.getString(R.string.network_type_unknown)
|
|
}
|
|
context.getString(R.string.network_connected, typeName)
|
|
} else {
|
|
context.getString(R.string.network_disconnected)
|
|
}
|
|
}
|
|
}
|
|
|
|
fun isConnected(context: Context): Boolean {
|
|
val cm = context.getSystemService(Context.CONNECTIVITY_SERVICE) as? ConnectivityManager
|
|
?: return false
|
|
return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
|
|
val network = cm.activeNetwork ?: return false
|
|
val caps = cm.getNetworkCapabilities(network) ?: return false
|
|
caps.hasCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET)
|
|
} else {
|
|
@Suppress("DEPRECATION")
|
|
cm.activeNetworkInfo?.isConnected == true
|
|
}
|
|
}
|
|
|
|
/**
|
|
* 实际尝试访问互联网(需在后台线程调用),并顺带利用响应 Date 头校准系统时钟异常状态。
|
|
* @return true 表示能连上目标 URL,false 表示超时或失败。
|
|
*/
|
|
fun isInternetReachable(): Boolean {
|
|
return try {
|
|
val url = URL(INTERNET_CHECK_URL)
|
|
val conn = url.openConnection() as? HttpURLConnection ?: return false
|
|
PlayerSsl.applyTo(conn, lenient = true)
|
|
conn.requestMethod = "HEAD"
|
|
conn.connectTimeout = INTERNET_CHECK_TIMEOUT_MS
|
|
conn.readTimeout = INTERNET_CHECK_TIMEOUT_MS
|
|
conn.instanceFollowRedirects = true
|
|
val code = conn.responseCode
|
|
updateServerDate(conn.date)
|
|
conn.disconnect()
|
|
code in 200..399
|
|
} catch (_: Exception) {
|
|
false
|
|
}
|
|
}
|
|
|
|
/**
|
|
* 获取局域网 IP 地址(优先 Wi-Fi / 有线)
|
|
*/
|
|
fun getLocalIpAddress(): String? {
|
|
try {
|
|
val interfaces = NetworkInterface.getNetworkInterfaces()?.toList() ?: return null
|
|
val sorted = interfaces.sortedWith { a, b ->
|
|
val aScore = if (a.name.startsWith("wlan") || a.name.startsWith("eth")) 0 else 1
|
|
val bScore = if (b.name.startsWith("wlan") || b.name.startsWith("eth")) 0 else 1
|
|
aScore.compareTo(bScore)
|
|
}
|
|
for (ni in sorted) {
|
|
if (ni.isLoopback || !ni.isUp) continue
|
|
for (addr in ni.inetAddresses) {
|
|
if (addr is Inet4Address && !addr.isLoopbackAddress && !addr.hostAddress.orEmpty().startsWith("127.")) {
|
|
return addr.hostAddress
|
|
}
|
|
}
|
|
}
|
|
} catch (_: Exception) { }
|
|
return null
|
|
}
|
|
}
|