Better network connection observing

This commit is contained in:
topjohnwu 2020-09-27 21:21:38 -07:00
parent ee4db43136
commit 9bebe07d5a
7 changed files with 91 additions and 145 deletions

View File

@ -3,9 +3,9 @@ package com.topjohnwu.magisk.core
import androidx.databinding.ObservableBoolean import androidx.databinding.ObservableBoolean
import com.topjohnwu.magisk.DynAPK import com.topjohnwu.magisk.DynAPK
import com.topjohnwu.magisk.core.model.UpdateInfo import com.topjohnwu.magisk.core.model.UpdateInfo
import com.topjohnwu.magisk.core.utils.net.NetworkObserver
import com.topjohnwu.magisk.ktx.get import com.topjohnwu.magisk.ktx.get
import com.topjohnwu.magisk.utils.CachedValue import com.topjohnwu.magisk.utils.CachedValue
import com.topjohnwu.magisk.utils.net.NetworkObserver
import com.topjohnwu.superuser.Shell import com.topjohnwu.superuser.Shell
import com.topjohnwu.superuser.ShellUtils.fastCmd import com.topjohnwu.superuser.ShellUtils.fastCmd
import com.topjohnwu.superuser.internal.UiThreadHandler import com.topjohnwu.superuser.internal.UiThreadHandler
@ -38,7 +38,7 @@ object Info {
val isConnected by lazy { val isConnected by lazy {
ObservableBoolean(false).also { field -> ObservableBoolean(false).also { field ->
NetworkObserver.observe(get()) { NetworkObserver.observe(get()) {
UiThreadHandler.run { field.set(it.isAvailable) } UiThreadHandler.run { field.set(it) }
} }
} }
} }

View File

@ -1,4 +1,4 @@
package com.topjohnwu.magisk.utils.net package com.topjohnwu.magisk.core.utils.net
import android.annotation.TargetApi import android.annotation.TargetApi
import android.content.Context import android.content.Context
@ -6,11 +6,12 @@ import android.net.ConnectivityManager
import android.net.Network import android.net.Network
import android.net.NetworkCapabilities import android.net.NetworkCapabilities
import android.net.NetworkRequest import android.net.NetworkRequest
import androidx.collection.ArraySet
@TargetApi(21) @TargetApi(21)
open class LollipopNetworkObserver( open class LollipopNetworkObserver(
context: Context, context: Context,
callback: NetInfoCallback callback: ConnectionCallback
): NetworkObserver(context, callback) { ): NetworkObserver(context, callback) {
private val networkCallback = NetCallback() private val networkCallback = NetCallback()
@ -22,17 +23,27 @@ open class LollipopNetworkObserver(
manager.registerNetworkCallback(request, networkCallback) manager.registerNetworkCallback(request, networkCallback)
} }
@Suppress("DEPRECATION")
override fun getCurrentState() {
callback(manager.activeNetworkInfo?.isConnected ?: false)
}
override fun stopObserving() { override fun stopObserving() {
manager.unregisterNetworkCallback(networkCallback) manager.unregisterNetworkCallback(networkCallback)
} }
private inner class NetCallback : ConnectivityManager.NetworkCallback() { private inner class NetCallback : ConnectivityManager.NetworkCallback() {
private val activeList = ArraySet<Network>()
override fun onAvailable(network: Network) { override fun onAvailable(network: Network) {
emit(Connectivity.create(manager, network)) activeList.add(network)
callback(true)
} }
override fun onLost(network: Network) { override fun onLost(network: Network) {
emit(Connectivity.create(manager, network)) activeList.remove(network)
callback(!activeList.isEmpty())
} }
} }
} }

View File

@ -0,0 +1,53 @@
@file:Suppress("DEPRECATION")
package com.topjohnwu.magisk.core.utils.net
import android.annotation.TargetApi
import android.content.BroadcastReceiver
import android.content.Context
import android.content.Intent
import android.content.IntentFilter
import android.net.NetworkCapabilities
import android.os.PowerManager
import androidx.core.content.getSystemService
@TargetApi(23)
class MarshmallowNetworkObserver(
context: Context,
callback: ConnectionCallback
): LollipopNetworkObserver(context, callback) {
private val receiver = IdleBroadcastReceiver()
init {
val filter = IntentFilter(PowerManager.ACTION_DEVICE_IDLE_MODE_CHANGED)
app.registerReceiver(receiver, filter)
}
override fun stopObserving() {
super.stopObserving()
app.unregisterReceiver(receiver)
}
override fun getCurrentState() {
callback(manager.getNetworkCapabilities(manager.activeNetwork)
?.hasCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET) ?: false)
}
private inner class IdleBroadcastReceiver: BroadcastReceiver() {
private fun Context.isIdleMode(): Boolean {
val pwm = getSystemService<PowerManager>() ?: return true
val isIgnoringOptimizations = pwm.isIgnoringBatteryOptimizations(packageName)
return pwm.isDeviceIdleMode && !isIgnoringOptimizations
}
override fun onReceive(context: Context, intent: Intent) {
if (context.isIdleMode()) {
callback(false)
} else {
getCurrentState()
}
}
}
}

View File

@ -1,40 +1,30 @@
package com.topjohnwu.magisk.utils.net package com.topjohnwu.magisk.core.utils.net
import android.content.Context import android.content.Context
import android.net.ConnectivityManager import android.net.ConnectivityManager
import android.os.Build import android.os.Build
import androidx.core.content.getSystemService import androidx.core.content.getSystemService
typealias NetInfoCallback = (Connectivity) -> Unit typealias ConnectionCallback = (Boolean) -> Unit
abstract class NetworkObserver protected constructor( abstract class NetworkObserver(
context: Context, context: Context,
private val callback: NetInfoCallback protected val callback: ConnectionCallback
) { ) {
protected val context = context.applicationContext protected val app: Context = context.applicationContext
protected val manager = context.getSystemService<ConnectivityManager>()!! protected val manager = context.getSystemService<ConnectivityManager>()!!
protected var last = Connectivity.create(manager)
init {
callback(last)
}
protected open fun emit(current: Connectivity) {
if (last != current)
callback(current)
last = current
}
protected abstract fun stopObserving() protected abstract fun stopObserving()
protected abstract fun getCurrentState()
companion object { companion object {
fun observe(context: Context, callback: NetInfoCallback): NetworkObserver { fun observe(context: Context, callback: ConnectionCallback): NetworkObserver {
return when (Build.VERSION.SDK_INT) { return when (Build.VERSION.SDK_INT) {
in 23 until Int.MAX_VALUE -> MarshmallowNetworkObserver(context, callback) in 23 until Int.MAX_VALUE -> MarshmallowNetworkObserver(context, callback)
in 21 until 23 -> LollipopNetworkObserver(context, callback) in 21 until 23 -> LollipopNetworkObserver(context, callback)
else -> PreLollipopNetworkObserver(context, callback) else -> PreLollipopNetworkObserver(context, callback)
} }.apply { getCurrentState() }
} }
} }
} }

View File

@ -1,34 +1,38 @@
@file:Suppress("DEPRECATION") @file:Suppress("DEPRECATION")
package com.topjohnwu.magisk.utils.net package com.topjohnwu.magisk.core.utils.net
import android.content.BroadcastReceiver import android.content.BroadcastReceiver
import android.content.Context import android.content.Context
import android.content.Intent import android.content.Intent
import android.content.IntentFilter import android.content.IntentFilter
import android.net.ConnectivityManager import android.net.ConnectivityManager
import androidx.core.net.ConnectivityManagerCompat
class PreLollipopNetworkObserver( class PreLollipopNetworkObserver(
context: Context, context: Context,
callback: NetInfoCallback callback: ConnectionCallback
): NetworkObserver(context, callback) { ): NetworkObserver(context, callback) {
private val receiver = ConnectivityBroadcastReceiver() private val receiver = ConnectivityBroadcastReceiver()
init { init {
val filter = IntentFilter() val filter = IntentFilter(ConnectivityManager.CONNECTIVITY_ACTION)
filter.addAction(ConnectivityManager.CONNECTIVITY_ACTION) app.registerReceiver(receiver, filter)
context.registerReceiver(receiver, filter)
} }
override fun stopObserving() { override fun stopObserving() {
context.unregisterReceiver(receiver) app.unregisterReceiver(receiver)
}
override fun getCurrentState() {
callback(manager.activeNetworkInfo?.isConnected ?: false)
} }
private inner class ConnectivityBroadcastReceiver: BroadcastReceiver() { private inner class ConnectivityBroadcastReceiver: BroadcastReceiver() {
override fun onReceive(context: Context?, intent: Intent?) { override fun onReceive(context: Context?, intent: Intent) {
emit(Connectivity.create(manager)) val info = ConnectivityManagerCompat.getNetworkInfoFromBroadcast(manager, intent)
callback(info?.isConnected ?: false)
} }
} }
} }

View File

@ -1,49 +0,0 @@
@file:Suppress("DEPRECATION")
package com.topjohnwu.magisk.utils.net
import android.net.ConnectivityManager
import android.net.Network
import android.net.NetworkInfo
import android.net.NetworkInfo.DetailedState
import androidx.annotation.RequiresApi
// Recreate NetworkInfo with Kotlin data class
data class Connectivity(
val state : NetworkInfo.State = NetworkInfo.State.DISCONNECTED,
val detailedState : DetailedState = DetailedState.IDLE,
val type : Int = -1,
val subType : Int = -1,
val isAvailable : Boolean = false,
val isFailover : Boolean = false,
val isRoaming : Boolean = false,
val typeName : String = "NONE",
val subTypeName : String = "NONE",
val reason : String? = null,
val extraInfo : String? = null
) {
private constructor(info: NetworkInfo) : this(
info.state,
info.detailedState,
info.type,
info.subtype,
info.isAvailable,
info.isFailover,
info.isRoaming,
info.typeName,
info.subtypeName,
info.reason,
info.extraInfo
)
companion object {
fun create(manager: ConnectivityManager): Connectivity {
return manager.activeNetworkInfo?.let { Connectivity(it) } ?: Connectivity()
}
@RequiresApi(21)
fun create(manager: ConnectivityManager, network: Network): Connectivity {
return manager.getNetworkInfo(network)?.let { Connectivity(it) } ?: Connectivity()
}
}
}

View File

@ -1,63 +0,0 @@
@file:Suppress("DEPRECATION")
package com.topjohnwu.magisk.utils.net
import android.annotation.TargetApi
import android.content.BroadcastReceiver
import android.content.Context
import android.content.Intent
import android.content.IntentFilter
import android.net.NetworkInfo
import android.os.PowerManager
import androidx.core.content.getSystemService
@TargetApi(23)
class MarshmallowNetworkObserver(
context: Context,
callback: NetInfoCallback
): LollipopNetworkObserver(context, callback) {
private val idleReceiver = IdleBroadcastReceiver()
init {
val filter = IntentFilter(PowerManager.ACTION_DEVICE_IDLE_MODE_CHANGED)
context.registerReceiver(idleReceiver, filter)
}
override fun stopObserving() {
super.stopObserving()
context.unregisterReceiver(idleReceiver)
}
override fun emit(current: Connectivity) {
val typeChanged = last.type != current.type
val wasConnected = last.state == NetworkInfo.State.CONNECTED
val isDisconnected = current.state == NetworkInfo.State.DISCONNECTED
val isNotIdle = current.detailedState != NetworkInfo.DetailedState.IDLE
if (typeChanged && wasConnected && isDisconnected && isNotIdle) {
super.emit(current)
super.emit(last)
last = current
} else {
super.emit(current)
}
}
private inner class IdleBroadcastReceiver: BroadcastReceiver() {
private fun isIdleMode(context: Context): Boolean {
val packageName = context.packageName
val manager = context.getSystemService<PowerManager>()!!
val isIgnoringOptimizations = manager.isIgnoringBatteryOptimizations(packageName)
return manager.isDeviceIdleMode && !isIgnoringOptimizations
}
override fun onReceive(context: Context, intent: Intent) {
if (isIdleMode(context)) {
emit(Connectivity())
} else {
emit(Connectivity.create(manager))
}
}
}
}