Commit 9bebe07d authored by topjohnwu's avatar topjohnwu

Better network connection observing

parent ee4db431
...@@ -3,9 +3,9 @@ package com.topjohnwu.magisk.core ...@@ -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 { ...@@ -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) }
} }
} }
} }
......
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 ...@@ -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( ...@@ -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())
} }
} }
} }
@file:Suppress("DEPRECATION") @file:Suppress("DEPRECATION")
package com.topjohnwu.magisk.utils.net package com.topjohnwu.magisk.core.utils.net
import android.annotation.TargetApi import android.annotation.TargetApi
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.NetworkInfo import android.net.NetworkCapabilities
import android.os.PowerManager import android.os.PowerManager
import androidx.core.content.getSystemService import androidx.core.content.getSystemService
@TargetApi(23) @TargetApi(23)
class MarshmallowNetworkObserver( class MarshmallowNetworkObserver(
context: Context, context: Context,
callback: NetInfoCallback callback: ConnectionCallback
): LollipopNetworkObserver(context, callback) { ): LollipopNetworkObserver(context, callback) {
private val idleReceiver = IdleBroadcastReceiver() private val receiver = IdleBroadcastReceiver()
init { init {
val filter = IntentFilter(PowerManager.ACTION_DEVICE_IDLE_MODE_CHANGED) val filter = IntentFilter(PowerManager.ACTION_DEVICE_IDLE_MODE_CHANGED)
context.registerReceiver(idleReceiver, filter) app.registerReceiver(receiver, filter)
} }
override fun stopObserving() { override fun stopObserving() {
super.stopObserving() super.stopObserving()
context.unregisterReceiver(idleReceiver) app.unregisterReceiver(receiver)
} }
override fun emit(current: Connectivity) { override fun getCurrentState() {
val typeChanged = last.type != current.type callback(manager.getNetworkCapabilities(manager.activeNetwork)
val wasConnected = last.state == NetworkInfo.State.CONNECTED ?.hasCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET) ?: false)
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 inner class IdleBroadcastReceiver: BroadcastReceiver() {
private fun isIdleMode(context: Context): Boolean { private fun Context.isIdleMode(): Boolean {
val packageName = context.packageName val pwm = getSystemService<PowerManager>() ?: return true
val manager = context.getSystemService<PowerManager>()!! val isIgnoringOptimizations = pwm.isIgnoringBatteryOptimizations(packageName)
val isIgnoringOptimizations = manager.isIgnoringBatteryOptimizations(packageName) return pwm.isDeviceIdleMode && !isIgnoringOptimizations
return manager.isDeviceIdleMode && !isIgnoringOptimizations
} }
override fun onReceive(context: Context, intent: Intent) { override fun onReceive(context: Context, intent: Intent) {
if (isIdleMode(context)) { if (context.isIdleMode()) {
emit(Connectivity()) callback(false)
} else { } else {
emit(Connectivity.create(manager)) getCurrentState()
} }
} }
} }
......
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() }
} }
} }
} }
@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)
} }
} }
} }
@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()
}
}
}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment