Correctly handle TetherType changing

This commit is contained in:
Mygod
2020-05-29 21:20:53 -04:00
parent 299085293d
commit 069b32a7d9
7 changed files with 156 additions and 83 deletions

View File

@@ -2,14 +2,18 @@ package be.mygod.vpnhotspot
import android.annotation.TargetApi import android.annotation.TargetApi
import android.os.Build import android.os.Build
import androidx.core.os.BuildCompat
import be.mygod.vpnhotspot.App.Companion.app import be.mygod.vpnhotspot.App.Companion.app
import be.mygod.vpnhotspot.net.Routing import be.mygod.vpnhotspot.net.Routing
import be.mygod.vpnhotspot.net.TetherType
import be.mygod.vpnhotspot.net.TetheringManager
import be.mygod.vpnhotspot.net.wifi.WifiDoubleLock import be.mygod.vpnhotspot.net.wifi.WifiDoubleLock
import be.mygod.vpnhotspot.widget.SmartSnackbar import be.mygod.vpnhotspot.widget.SmartSnackbar
import timber.log.Timber import timber.log.Timber
import java.net.NetworkInterface import java.net.NetworkInterface
abstract class RoutingManager(private val caller: Any, val downstream: String, private val isWifi: Boolean) { abstract class RoutingManager(private val caller: Any, val downstream: String,
private val forceWifi: Boolean = false) : TetheringManager.TetheringEventCallback {
companion object { companion object {
private const val KEY_MASQUERADE_MODE = "service.masqueradeMode" private const val KEY_MASQUERADE_MODE = "service.masqueradeMode"
var masqueradeMode: Routing.MasqueradeMode var masqueradeMode: Routing.MasqueradeMode
@@ -54,10 +58,12 @@ abstract class RoutingManager(private val caller: Any, val downstream: String, p
val started get() = active[downstream] === this val started get() = active[downstream] === this
private var routing: Routing? = null private var routing: Routing? = null
private var isWifi = forceWifi || TetherType.ofInterface(downstream).isWifi
fun start() = when (val other = active.putIfAbsent(downstream, this)) { fun start() = when (val other = active.putIfAbsent(downstream, this)) {
null -> { null -> {
if (isWifi) WifiDoubleLock.acquire(this) if (isWifi) WifiDoubleLock.acquire(this)
if (!forceWifi && BuildCompat.isAtLeastR()) TetheringManager.registerTetheringEventCallback(null, this)
initRouting() initRouting()
} }
this -> true // already started this -> true // already started
@@ -66,6 +72,13 @@ abstract class RoutingManager(private val caller: Any, val downstream: String, p
open fun ifaceHandler(iface: NetworkInterface) { } open fun ifaceHandler(iface: NetworkInterface) { }
override fun onTetherableInterfaceRegexpsChanged() {
val isWifiNow = TetherType.ofInterface(downstream).isWifi
if (isWifi == isWifiNow) return
if (isWifi) WifiDoubleLock.release(this) else WifiDoubleLock.acquire(this)
isWifi = isWifiNow
}
private fun initRouting() = try { private fun initRouting() = try {
routing = Routing(caller, downstream, this::ifaceHandler).apply { routing = Routing(caller, downstream, this::ifaceHandler).apply {
try { try {
@@ -87,6 +100,7 @@ abstract class RoutingManager(private val caller: Any, val downstream: String, p
fun stop() { fun stop() {
if (active.remove(downstream, this)) { if (active.remove(downstream, this)) {
if (!forceWifi && BuildCompat.isAtLeastR()) TetheringManager.unregisterTetheringEventCallback(this)
if (isWifi) WifiDoubleLock.release(this) if (isWifi) WifiDoubleLock.release(this)
routing?.revert() routing?.revert()
} }

View File

@@ -4,14 +4,12 @@ import android.content.Intent
import androidx.annotation.RequiresApi import androidx.annotation.RequiresApi
import be.mygod.vpnhotspot.App.Companion.app import be.mygod.vpnhotspot.App.Companion.app
import be.mygod.vpnhotspot.net.Routing import be.mygod.vpnhotspot.net.Routing
import be.mygod.vpnhotspot.net.TetherType
import be.mygod.vpnhotspot.net.TetheringManager import be.mygod.vpnhotspot.net.TetheringManager
import be.mygod.vpnhotspot.net.monitor.IpNeighbourMonitor import be.mygod.vpnhotspot.net.monitor.IpNeighbourMonitor
import be.mygod.vpnhotspot.util.Event0 import be.mygod.vpnhotspot.util.Event0
import be.mygod.vpnhotspot.widget.SmartSnackbar import be.mygod.vpnhotspot.widget.SmartSnackbar
import kotlinx.coroutines.* import kotlinx.coroutines.*
import timber.log.Timber import timber.log.Timber
import java.lang.IllegalStateException
import java.util.concurrent.ConcurrentHashMap import java.util.concurrent.ConcurrentHashMap
class TetheringService : IpNeighbourMonitoringService(), TetheringManager.TetheringEventCallback, CoroutineScope { class TetheringService : IpNeighbourMonitoringService(), TetheringManager.TetheringEventCallback, CoroutineScope {
@@ -31,7 +29,7 @@ class TetheringService : IpNeighbourMonitoringService(), TetheringManager.Tether
} }
private inner class Downstream(caller: Any, downstream: String, var monitor: Boolean = false) : private inner class Downstream(caller: Any, downstream: String, var monitor: Boolean = false) :
RoutingManager(caller, downstream, TetherType.ofInterface(downstream).isWifi) { RoutingManager(caller, downstream) {
override fun Routing.configure() { override fun Routing.configure() {
forward() forward()
masquerade(masqueradeMode) masquerade(masqueradeMode)

View File

@@ -14,6 +14,7 @@ import android.view.ViewGroup
import android.widget.EditText import android.widget.EditText
import androidx.appcompat.app.AlertDialog import androidx.appcompat.app.AlertDialog
import androidx.appcompat.widget.PopupMenu import androidx.appcompat.widget.PopupMenu
import androidx.core.os.BuildCompat
import androidx.databinding.BaseObservable import androidx.databinding.BaseObservable
import androidx.fragment.app.Fragment import androidx.fragment.app.Fragment
import androidx.fragment.app.activityViewModels import androidx.fragment.app.activityViewModels
@@ -29,6 +30,7 @@ import be.mygod.vpnhotspot.Empty
import be.mygod.vpnhotspot.R import be.mygod.vpnhotspot.R
import be.mygod.vpnhotspot.databinding.FragmentClientsBinding import be.mygod.vpnhotspot.databinding.FragmentClientsBinding
import be.mygod.vpnhotspot.databinding.ListitemClientBinding import be.mygod.vpnhotspot.databinding.ListitemClientBinding
import be.mygod.vpnhotspot.net.TetheringManager
import be.mygod.vpnhotspot.net.monitor.IpNeighbourMonitor import be.mygod.vpnhotspot.net.monitor.IpNeighbourMonitor
import be.mygod.vpnhotspot.net.monitor.TrafficRecorder import be.mygod.vpnhotspot.net.monitor.TrafficRecorder
import be.mygod.vpnhotspot.room.AppDatabase import be.mygod.vpnhotspot.room.AppDatabase
@@ -45,7 +47,7 @@ import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext import kotlinx.coroutines.withContext
import java.text.NumberFormat import java.text.NumberFormat
class ClientsFragment : Fragment() { class ClientsFragment : Fragment(), TetheringManager.TetheringEventCallback {
@Parcelize @Parcelize
data class NicknameArg(val mac: Long, val nickname: CharSequence) : Parcelable data class NicknameArg(val mac: Long, val nickname: CharSequence) : Parcelable
class NicknameDialogFragment : AlertDialogFragment<NicknameArg, Empty>() { class NicknameDialogFragment : AlertDialogFragment<NicknameArg, Empty>() {
@@ -167,8 +169,10 @@ class ClientsFragment : Fragment() {
} }
private inner class ClientAdapter : ListAdapter<Client, ClientViewHolder>(Client) { private inner class ClientAdapter : ListAdapter<Client, ClientViewHolder>(Client) {
var size = 0
override fun submitList(list: MutableList<Client>?) { override fun submitList(list: MutableList<Client>?) {
super.submitList(list) super.submitList(list) { size = list?.size ?: 0 }
binding.swipeRefresher.isRefreshing = false binding.swipeRefresher.isRefreshing = false
} }
@@ -226,6 +230,7 @@ class ClientsFragment : Fragment() {
} }
override fun onStart() { override fun onStart() {
if (BuildCompat.isAtLeastR()) TetheringManager.registerTetheringEventCallback(null, this)
super.onStart() super.onStart()
// we just put these two thing together as this is the only place we need to use this event for now // we just put these two thing together as this is the only place we need to use this event for now
TrafficRecorder.foregroundListeners[this] = adapter::updateTraffic TrafficRecorder.foregroundListeners[this] = adapter::updateTraffic
@@ -239,5 +244,11 @@ class ClientsFragment : Fragment() {
override fun onStop() { override fun onStop() {
TrafficRecorder.foregroundListeners -= this TrafficRecorder.foregroundListeners -= this
super.onStop() super.onStop()
if (BuildCompat.isAtLeastR()) TetheringManager.unregisterTetheringEventCallback(this)
}
override fun onTetherableInterfaceRegexpsChanged() {
// icon might be changed due to TetherType changes
adapter.notifyItemRangeChanged(0, adapter.size)
} }
} }

View File

@@ -32,12 +32,13 @@ import be.mygod.vpnhotspot.util.ServiceForegroundConnector
import be.mygod.vpnhotspot.util.broadcastReceiver import be.mygod.vpnhotspot.util.broadcastReceiver
import be.mygod.vpnhotspot.util.isNotGone import be.mygod.vpnhotspot.util.isNotGone
import be.mygod.vpnhotspot.widget.SmartSnackbar import be.mygod.vpnhotspot.widget.SmartSnackbar
import kotlinx.coroutines.CompletableDeferred
import timber.log.Timber import timber.log.Timber
import java.lang.reflect.InvocationTargetException import java.lang.reflect.InvocationTargetException
import java.net.NetworkInterface import java.net.NetworkInterface
import java.net.SocketException import java.net.SocketException
class TetheringFragment : Fragment(), ServiceConnection, Toolbar.OnMenuItemClickListener { class TetheringFragment : Fragment(), ServiceConnection, Toolbar.OnMenuItemClickListener, TetheringManager.TetheringEventCallback {
companion object { companion object {
const val START_REPEATER = 4 const val START_REPEATER = 4
const val START_LOCAL_ONLY_HOTSPOT = 1 const val START_LOCAL_ONLY_HOTSPOT = 1
@@ -64,15 +65,36 @@ class TetheringFragment : Fragment(), ServiceConnection, Toolbar.OnMenuItemClick
TetherManager.WifiLegacy(this@TetheringFragment) TetherManager.WifiLegacy(this@TetheringFragment)
} }
private var enabledIfaces = emptyList<String>()
private var listDeferred = CompletableDeferred<List<Manager>>().apply { complete(emptyList()) }
private fun updateEnabledTypes() {
this@TetheringFragment.enabledTypes = enabledIfaces.map { TetherType.ofInterface(it) }.toSet()
}
suspend fun notifyInterfaceChanged(lastList: List<Manager>? = null) {
@Suppress("NAME_SHADOWING") val lastList = lastList ?: listDeferred.await()
val first = lastList.indexOfFirst { it is InterfaceManager }
if (first >= 0) notifyItemRangeChanged(first, lastList.indexOfLast { it is InterfaceManager } - first + 1)
}
suspend fun notifyTetherTypeChanged() {
updateEnabledTypes()
val lastList = listDeferred.await()
notifyInterfaceChanged(lastList)
val first = lastList.indexOfLast { it !is TetherManager } + 1
notifyItemRangeChanged(first, lastList.size - first)
}
fun update(activeIfaces: List<String>, localOnlyIfaces: List<String>, erroredIfaces: List<String>) { fun update(activeIfaces: List<String>, localOnlyIfaces: List<String>, erroredIfaces: List<String>) {
val deferred = CompletableDeferred<List<Manager>>()
listDeferred = deferred
ifaceLookup = try { ifaceLookup = try {
NetworkInterface.getNetworkInterfaces().asSequence().associateBy { it.name } NetworkInterface.getNetworkInterfaces().asSequence().associateBy { it.name }
} catch (e: SocketException) { } catch (e: SocketException) {
Timber.d(e) Timber.d(e)
emptyMap() emptyMap()
} }
this@TetheringFragment.enabledTypes = enabledIfaces = activeIfaces + localOnlyIfaces
(activeIfaces + localOnlyIfaces).map { TetherType.ofInterface(it) }.toSet() updateEnabledTypes()
val list = ArrayList<Manager>() val list = ArrayList<Manager>()
if (RepeaterService.supported) list.add(repeaterManager) if (RepeaterService.supported) list.add(repeaterManager)
@@ -94,7 +116,7 @@ class TetheringFragment : Fragment(), ServiceConnection, Toolbar.OnMenuItemClick
list.add(wifiManagerLegacy) list.add(wifiManagerLegacy)
wifiManagerLegacy.onTetheringStarted() wifiManagerLegacy.onTetheringStarted()
} }
submitList(list) submitList(list) { deferred.complete(list) }
} }
override fun getItemViewType(position: Int) = getItem(position).type override fun getItemViewType(position: Int) = getItem(position).type
@@ -230,18 +252,20 @@ class TetheringFragment : Fragment(), ServiceConnection, Toolbar.OnMenuItemClick
override fun onServiceConnected(name: ComponentName?, service: IBinder?) { override fun onServiceConnected(name: ComponentName?, service: IBinder?) {
binder = service as TetheringService.Binder binder = service as TetheringService.Binder
service.routingsChanged[this] = { service.routingsChanged[this] = {
requireContext().apply { lifecycleScope.launchWhenStarted { adapter.notifyInterfaceChanged() }
// flush tethered interfaces
unregisterReceiver(receiver)
registerReceiver(receiver, IntentFilter(TetheringManager.ACTION_TETHER_STATE_CHANGED))
}
} }
requireContext().registerReceiver(receiver, IntentFilter(TetheringManager.ACTION_TETHER_STATE_CHANGED)) requireContext().registerReceiver(receiver, IntentFilter(TetheringManager.ACTION_TETHER_STATE_CHANGED))
if (BuildCompat.isAtLeastR()) TetheringManager.registerTetheringEventCallback(null, this)
} }
override fun onServiceDisconnected(name: ComponentName?) { override fun onServiceDisconnected(name: ComponentName?) {
(binder ?: return).routingsChanged -= this (binder ?: return).routingsChanged -= this
binder = null binder = null
if (BuildCompat.isAtLeastR()) TetheringManager.unregisterTetheringEventCallback(this)
requireContext().unregisterReceiver(receiver) requireContext().unregisterReceiver(receiver)
} }
override fun onTetherableInterfaceRegexpsChanged() {
lifecycleScope.launchWhenStarted { adapter.notifyTetherTypeChanged() }
}
} }

View File

@@ -10,6 +10,7 @@ import android.service.quicksettings.Tile
import android.widget.Toast import android.widget.Toast
import androidx.annotation.RequiresApi import androidx.annotation.RequiresApi
import androidx.core.content.ContextCompat import androidx.core.content.ContextCompat
import androidx.core.os.BuildCompat
import be.mygod.vpnhotspot.R import be.mygod.vpnhotspot.R
import be.mygod.vpnhotspot.TetheringService import be.mygod.vpnhotspot.TetheringService
import be.mygod.vpnhotspot.net.TetherType import be.mygod.vpnhotspot.net.TetherType
@@ -25,7 +26,8 @@ import java.io.IOException
import java.lang.reflect.InvocationTargetException import java.lang.reflect.InvocationTargetException
@RequiresApi(24) @RequiresApi(24)
sealed class TetheringTileService : KillableTileService(), TetheringManager.StartTetheringCallback { sealed class TetheringTileService : KillableTileService(), TetheringManager.StartTetheringCallback,
TetheringManager.TetheringEventCallback {
protected val tileOff by lazy { Icon.createWithResource(application, icon) } protected val tileOff by lazy { Icon.createWithResource(application, icon) }
protected val tileOn by lazy { Icon.createWithResource(application, R.drawable.ic_quick_settings_tile_on) } protected val tileOn by lazy { Icon.createWithResource(application, R.drawable.ic_quick_settings_tile_on) }
@@ -47,12 +49,15 @@ sealed class TetheringTileService : KillableTileService(), TetheringManager.Star
override fun onStartListening() { override fun onStartListening() {
super.onStartListening() super.onStartListening()
bindService(Intent(this, TetheringService::class.java), this, Context.BIND_AUTO_CREATE) bindService(Intent(this, TetheringService::class.java), this, Context.BIND_AUTO_CREATE)
// we need to initialize tethered ASAP for onClick, which is not achievable using registerTetheringEventCallback
tethered = registerReceiver(receiver, IntentFilter(TetheringManager.ACTION_TETHER_STATE_CHANGED)) tethered = registerReceiver(receiver, IntentFilter(TetheringManager.ACTION_TETHER_STATE_CHANGED))
?.tetheredIfaces ?.tetheredIfaces
if (BuildCompat.isAtLeastR()) TetheringManager.registerTetheringEventCallback(null, this)
updateTile() updateTile()
} }
override fun onStopListening() { override fun onStopListening() {
if (BuildCompat.isAtLeastR()) TetheringManager.unregisterTetheringEventCallback(this)
unregisterReceiver(receiver) unregisterReceiver(receiver)
stopAndUnbind(this) stopAndUnbind(this)
super.onStopListening() super.onStopListening()
@@ -60,7 +65,7 @@ sealed class TetheringTileService : KillableTileService(), TetheringManager.Star
override fun onServiceConnected(name: ComponentName?, service: IBinder?) { override fun onServiceConnected(name: ComponentName?, service: IBinder?) {
binder = service as TetheringService.Binder binder = service as TetheringService.Binder
service.routingsChanged[this] = { updateTile() } service.routingsChanged[this] = this::updateTile
super.onServiceConnected(name, service) super.onServiceConnected(name, service)
} }
@@ -127,6 +132,7 @@ sealed class TetheringTileService : KillableTileService(), TetheringManager.Star
error?.let { Toast.makeText(this, TetheringManager.tetherErrorMessage(it), Toast.LENGTH_LONG).show() } error?.let { Toast.makeText(this, TetheringManager.tetherErrorMessage(it), Toast.LENGTH_LONG).show() }
updateTile() updateTile()
} }
override fun onTetherableInterfaceRegexpsChanged() = updateTile()
class Wifi : TetheringTileService() { class Wifi : TetheringTileService() {
override val labelString get() = R.string.tethering_manage_wifi override val labelString get() = R.string.tethering_manage_wifi

View File

@@ -1,6 +1,7 @@
package be.mygod.vpnhotspot.net package be.mygod.vpnhotspot.net
import android.content.res.Resources import android.content.res.Resources
import androidx.annotation.RequiresApi
import androidx.core.os.BuildCompat import androidx.core.os.BuildCompat
import be.mygod.vpnhotspot.App.Companion.app import be.mygod.vpnhotspot.App.Companion.app
import be.mygod.vpnhotspot.R import be.mygod.vpnhotspot.R
@@ -25,39 +26,50 @@ enum class TetherType {
else -> false else -> false
} }
companion object { companion object : TetheringManager.TetheringEventCallback {
private val usbRegexs: List<Pattern> private lateinit var usbRegexs: List<Pattern>
private val wifiRegexs: List<Pattern> private lateinit var wifiRegexs: List<Pattern>
private val wifiP2pRegexs: List<Pattern> private var wifiP2pRegexs = emptyList<Pattern>()
private val wimaxRegexs: List<Pattern> private val wimaxRegexs: List<Pattern>
private val bluetoothRegexs: List<Pattern> private lateinit var bluetoothRegexs: List<Pattern>
private val ncmRegexs: List<Pattern> private var ncmRegexs = emptyList<Pattern>()
private val ethernetRegex: Pattern? private val ethernetRegex: Pattern?
private var requiresUpdate = true
private fun Pair<String?, Resources>.getRegexs(name: String) = second private fun Pair<String?, Resources>.getRegexs(name: String) = second
.getStringArray(second.getIdentifier(name, "array", first)) .getStringArray(second.getIdentifier(name, "array", first))
.filterNotNull() .filterNotNull()
.map { it.toPattern() } .map { it.toPattern() }
@RequiresApi(30)
private fun updateRegexs() {
requiresUpdate = false
val tethering = TetheringManager.PACKAGE to app.packageManager.getResourcesForApplication(
TetheringManager.resolvedService.serviceInfo.applicationInfo)
usbRegexs = tethering.getRegexs("config_tether_usb_regexs")
wifiRegexs = tethering.getRegexs("config_tether_wifi_regexs")
wifiP2pRegexs = tethering.getRegexs("config_tether_wifi_p2p_regexs")
bluetoothRegexs = tethering.getRegexs("config_tether_bluetooth_regexs")
ncmRegexs = tethering.getRegexs("config_tether_ncm_regexs")
}
@RequiresApi(30)
override fun onTetherableInterfaceRegexpsChanged() {
requiresUpdate = true
}
/** /**
* Source: https://android.googlesource.com/platform/frameworks/base/+/32e772f/packages/Tethering/src/com/android/networkstack/tethering/TetheringConfiguration.java#93 * Source: https://android.googlesource.com/platform/frameworks/base/+/32e772f/packages/Tethering/src/com/android/networkstack/tethering/TetheringConfiguration.java#93
*/ */
init { init {
val system = "android" to Resources.getSystem() val system = "android" to Resources.getSystem()
if (BuildCompat.isAtLeastR()) { if (BuildCompat.isAtLeastR()) {
val tethering = TetheringManager.PACKAGE to app.packageManager.getResourcesForApplication( TetheringManager.registerTetheringEventCallback(null, this)
TetheringManager.resolvedService.serviceInfo.applicationInfo) updateRegexs()
usbRegexs = tethering.getRegexs("config_tether_usb_regexs")
wifiRegexs = tethering.getRegexs("config_tether_wifi_regexs")
wifiP2pRegexs = tethering.getRegexs("config_tether_wifi_p2p_regexs")
bluetoothRegexs = tethering.getRegexs("config_tether_bluetooth_regexs")
ncmRegexs = tethering.getRegexs("config_tether_ncm_regexs")
} else { } else {
usbRegexs = system.getRegexs("config_tether_usb_regexs") usbRegexs = system.getRegexs("config_tether_usb_regexs")
wifiRegexs = system.getRegexs("config_tether_wifi_regexs") wifiRegexs = system.getRegexs("config_tether_wifi_regexs")
wifiP2pRegexs = emptyList()
bluetoothRegexs = system.getRegexs("config_tether_bluetooth_regexs") bluetoothRegexs = system.getRegexs("config_tether_bluetooth_regexs")
ncmRegexs = emptyList()
} }
wimaxRegexs = system.getRegexs("config_tether_wimax_regexs") wimaxRegexs = system.getRegexs("config_tether_wimax_regexs")
// available since Android 4.0: https://android.googlesource.com/platform/frameworks/base/+/c96a667162fab44a250503caccb770109a9cb69a // available since Android 4.0: https://android.googlesource.com/platform/frameworks/base/+/c96a667162fab44a250503caccb770109a9cb69a
@@ -66,11 +78,18 @@ enum class TetherType {
} }
/** /**
* The result could change for the same interface since API 30+.
* It will be triggered by [TetheringManager.TetheringEventCallback.onTetherableInterfaceRegexpsChanged].
*
* Based on: https://android.googlesource.com/platform/frameworks/base/+/5d36f01/packages/Tethering/src/com/android/networkstack/tethering/Tethering.java#479 * Based on: https://android.googlesource.com/platform/frameworks/base/+/5d36f01/packages/Tethering/src/com/android/networkstack/tethering/Tethering.java#479
*/ */
fun ofInterface(iface: String?, p2pDev: String? = null) = when { tailrec fun ofInterface(iface: String?, p2pDev: String? = null): TetherType = when {
iface == null -> NONE iface == null -> NONE
iface == p2pDev -> WIFI_P2P iface == p2pDev -> WIFI_P2P
requiresUpdate -> {
if (BuildCompat.isAtLeastR()) updateRegexs() else error("unexpected requiresUpdate")
ofInterface(iface, p2pDev)
}
wifiRegexs.any { it.matcher(iface).matches() } -> WIFI wifiRegexs.any { it.matcher(iface).matches() } -> WIFI
wifiP2pRegexs.any { it.matcher(iface).matches() } -> WIFI_P2P wifiP2pRegexs.any { it.matcher(iface).matches() } -> WIFI_P2P
usbRegexs.any { it.matcher(iface).matches() } -> USB usbRegexs.any { it.matcher(iface).matches() } -> USB

View File

@@ -22,7 +22,9 @@ import be.mygod.vpnhotspot.util.ensureReceiverUnregistered
import com.android.dx.stock.ProxyBuilder import com.android.dx.stock.ProxyBuilder
import timber.log.Timber import timber.log.Timber
import java.lang.ref.WeakReference import java.lang.ref.WeakReference
import java.lang.reflect.InvocationHandler
import java.lang.reflect.InvocationTargetException import java.lang.reflect.InvocationTargetException
import java.lang.reflect.Method
import java.lang.reflect.Proxy import java.lang.reflect.Proxy
import java.util.concurrent.Executor import java.util.concurrent.Executor
@@ -359,6 +361,8 @@ object TetheringManager {
* This will be called immediately after the callback is registered, and may be called * This will be called immediately after the callback is registered, and may be called
* multiple times later upon changes. * multiple times later upon changes.
* *
* CHANGED: This method will NOT be immediately called after registration.
*
* *@param reg The new regular expressions. * *@param reg The new regular expressions.
* @hide * @hide
*/ */
@@ -446,64 +450,61 @@ object TetheringManager {
* @param callback the callback to be called when tethering has change events. * @param callback the callback to be called when tethering has change events.
*/ */
@RequiresApi(30) @RequiresApi(30)
fun registerTetheringEventCallback(executor: Executor, callback: TetheringEventCallback) { fun registerTetheringEventCallback(executor: Executor?, callback: TetheringEventCallback) {
val reference = WeakReference(callback) val reference = WeakReference(callback)
val proxy = synchronized(callbackMap) { val proxy = synchronized(callbackMap) {
callbackMap.computeIfAbsent(callback) { callbackMap.computeIfAbsent(callback) {
Proxy.newProxyInstance(interfaceTetheringEventCallback.classLoader, Proxy.newProxyInstance(interfaceTetheringEventCallback.classLoader,
arrayOf(interfaceTetheringEventCallback)) { proxy, method, args -> arrayOf(interfaceTetheringEventCallback), object : InvocationHandler {
@Suppress("NAME_SHADOWING") val callback = reference.get() private var regexpsSent = false
when (val name = method.name) { override fun invoke(proxy: Any, method: Method, args: Array<out Any?>): Any? {
"onTetheringSupported" -> { @Suppress("NAME_SHADOWING") val callback = reference.get()
if (args.size > 1) Timber.w("Unexpected args for $name: $args") when (val name = method.name) {
callback?.onTetheringSupported(args[0] as Boolean) "onTetheringSupported" -> {
null if (args.size > 1) Timber.w("Unexpected args for $name: $args")
} callback?.onTetheringSupported(args[0] as Boolean)
"onUpstreamChanged" -> { }
if (args.size > 1) Timber.w("Unexpected args for $name: $args") "onUpstreamChanged" -> {
callback?.onUpstreamChanged(args[0] as Network?) if (args.size > 1) Timber.w("Unexpected args for $name: $args")
null callback?.onUpstreamChanged(args[0] as Network?)
} }
"onTetherableInterfaceRegexpsChanged" -> { "onTetherableInterfaceRegexpsChanged" -> {
callback?.onTetherableInterfaceRegexpsChanged() if (regexpsSent) callback?.onTetherableInterfaceRegexpsChanged()
null regexpsSent = true
} }
"onTetherableInterfacesChanged" -> { "onTetherableInterfacesChanged" -> {
if (args.size > 1) Timber.w("Unexpected args for $name: $args") if (args.size > 1) Timber.w("Unexpected args for $name: $args")
@Suppress("UNCHECKED_CAST") @Suppress("UNCHECKED_CAST")
callback?.onTetherableInterfacesChanged(args[0] as List<String?>) callback?.onTetherableInterfacesChanged(args[0] as List<String?>)
null }
} "onTetheredInterfacesChanged" -> {
"onTetheredInterfacesChanged" -> { if (args.size > 1) Timber.w("Unexpected args for $name: $args")
if (args.size > 1) Timber.w("Unexpected args for $name: $args") @Suppress("UNCHECKED_CAST")
@Suppress("UNCHECKED_CAST") callback?.onTetheredInterfacesChanged(args[0] as List<String?>)
callback?.onTetheredInterfacesChanged(args[0] as List<String?>) }
null "onError" -> {
} if (args.size > 2) Timber.w("Unexpected args for $name: $args")
"onError" -> { callback?.onError(args[0] as String, args[1] as Int)
if (args.size > 2) Timber.w("Unexpected args for $name: $args") }
callback?.onError(args[0] as String, args[1] as Int) "onClientsChanged" -> {
null if (args.size > 1) Timber.w("Unexpected args for $name: $args")
} callback?.onClientsChanged(args[0] as Iterable<*>)
"onClientsChanged" -> { }
if (args.size > 1) Timber.w("Unexpected args for $name: $args") "onOffloadStatusChanged" -> {
callback?.onClientsChanged(args[0] as Iterable<*>) if (args.size > 1) Timber.w("Unexpected args for $name: $args")
null callback?.onOffloadStatusChanged(args[0] as Int)
} }
"onOffloadStatusChanged" -> { else -> {
if (args.size > 1) Timber.w("Unexpected args for $name: $args") Timber.w("Unexpected method, calling super: $method")
callback?.onOffloadStatusChanged(args[0] as Int) return ProxyBuilder.callSuper(proxy, method, args)
null }
}
else -> {
Timber.w("Unexpected method, calling super: $method")
ProxyBuilder.callSuper(proxy, method, args)
} }
return null
} }
} })
} }
} }
registerTetheringEventCallback.invoke(instance, executor, proxy) registerTetheringEventCallback.invoke(instance, executor ?: null.makeExecutor(), proxy)
} }
/** /**
* Remove tethering event callback previously registered with * Remove tethering event callback previously registered with