diff --git a/mobile/src/main/java/be/mygod/vpnhotspot/NetUtils.kt b/mobile/src/main/java/be/mygod/vpnhotspot/NetUtils.kt index f368e20b..e4147074 100644 --- a/mobile/src/main/java/be/mygod/vpnhotspot/NetUtils.kt +++ b/mobile/src/main/java/be/mygod/vpnhotspot/NetUtils.kt @@ -1,19 +1,31 @@ package be.mygod.vpnhotspot -import android.net.ConnectivityManager +import android.os.Build +import android.os.Bundle +import android.support.annotation.RequiresApi import java.io.File object NetUtils { // hidden constants from ConnectivityManager + /** + * This is a sticky broadcast since almost forever. + * + * https://android.googlesource.com/platform/frameworks/base.git/+/2a091d7aa0c174986387e5d56bf97a87fe075bdb%5E%21/services/java/com/android/server/connectivity/Tethering.java + */ const val ACTION_TETHER_STATE_CHANGED = "android.net.conn.TETHER_STATE_CHANGED" - const val EXTRA_ACTIVE_TETHER = "tetherArray" + @Deprecated("No longer used on Android 8+ (API 26+)") + private const val EXTRA_ACTIVE_TETHER_LEGACY = "activeArray" + @RequiresApi(26) + private const val EXTRA_ACTIVE_LOCAL_ONLY = "localOnlyArray" + @RequiresApi(26) + private const val EXTRA_ACTIVE_TETHER = "tetherArray" private val spaces = " +".toPattern() private val mac = "^([0-9a-f]{2}:){5}[0-9a-f]{2}$".toPattern() - private val getTetheredIfaces = ConnectivityManager::class.java.getDeclaredMethod("getTetheredIfaces") - @Suppress("UNCHECKED_CAST") - val ConnectivityManager.tetheredIfaces get() = getTetheredIfaces.invoke(this) as Array + fun getTetheredIfaces(extras: Bundle) = if (Build.VERSION.SDK_INT >= 26) + extras.getStringArrayList(EXTRA_ACTIVE_TETHER).toSet() + extras.getStringArrayList(EXTRA_ACTIVE_LOCAL_ONLY) + else extras.getStringArrayList(EXTRA_ACTIVE_TETHER_LEGACY).toSet() fun arp(iface: String? = null) = File("/proc/net/arp").bufferedReader().useLines { // IP address HW type Flags HW address Mask Device diff --git a/mobile/src/main/java/be/mygod/vpnhotspot/TetheringFragment.kt b/mobile/src/main/java/be/mygod/vpnhotspot/TetheringFragment.kt index eaf91c36..df5a6bce 100644 --- a/mobile/src/main/java/be/mygod/vpnhotspot/TetheringFragment.kt +++ b/mobile/src/main/java/be/mygod/vpnhotspot/TetheringFragment.kt @@ -18,7 +18,6 @@ import android.view.LayoutInflater import android.view.View import android.view.ViewGroup import be.mygod.vpnhotspot.App.Companion.app -import be.mygod.vpnhotspot.NetUtils.tetheredIfaces import be.mygod.vpnhotspot.databinding.FragmentTetheringBinding import be.mygod.vpnhotspot.databinding.ListitemInterfaceBinding import be.mygod.vpnhotspot.widget.TextViewLinkHandler @@ -86,7 +85,7 @@ class TetheringFragment : Fragment() { inner class InterfaceAdapter : RecyclerView.Adapter() { private val tethered = SortedList(String::class.java, StringSorter) - fun update(data: Set = VpnListener.connectivityManager.tetheredIfaces.toSet()) { + fun update(data: Set) { val oldEmpty = tethered.size() == 0 tethered.clear() tethered.addAll(data) @@ -109,8 +108,7 @@ class TetheringFragment : Fragment() { private val receiver = broadcastReceiver { _, intent -> when (intent.action) { TetheringService.ACTION_ACTIVE_INTERFACES_CHANGED -> adapter.notifyDataSetChanged() - NetUtils.ACTION_TETHER_STATE_CHANGED -> - adapter.update(intent.extras.getStringArrayList(NetUtils.EXTRA_ACTIVE_TETHER).toSet()) + NetUtils.ACTION_TETHER_STATE_CHANGED -> adapter.update(NetUtils.getTetheredIfaces(intent.extras).toSet()) } } private var receiverRegistered = false @@ -133,7 +131,6 @@ class TetheringFragment : Fragment() { override fun onStart() { super.onStart() if (!receiverRegistered) { - adapter.update() val context = context!! context.registerReceiver(receiver, intentFilter(NetUtils.ACTION_TETHER_STATE_CHANGED)) LocalBroadcastManager.getInstance(context) diff --git a/mobile/src/main/java/be/mygod/vpnhotspot/TetheringService.kt b/mobile/src/main/java/be/mygod/vpnhotspot/TetheringService.kt index a61e7a51..4ba09429 100644 --- a/mobile/src/main/java/be/mygod/vpnhotspot/TetheringService.kt +++ b/mobile/src/main/java/be/mygod/vpnhotspot/TetheringService.kt @@ -4,7 +4,6 @@ import android.app.Service import android.content.Intent import android.support.v4.content.LocalBroadcastManager import be.mygod.vpnhotspot.App.Companion.app -import be.mygod.vpnhotspot.NetUtils.tetheredIfaces class TetheringService : Service(), VpnListener.Callback { companion object { @@ -25,7 +24,7 @@ class TetheringService : Service(), VpnListener.Callback { private var upstream: String? = null private var receiverRegistered = false private val receiver = broadcastReceiver { _, intent -> - val remove = routings.keys - intent.extras.getStringArrayList(NetUtils.EXTRA_ACTIVE_TETHER).toSet() + val remove = routings.keys - NetUtils.getTetheredIfaces(intent.extras) if (remove.isEmpty()) return@broadcastReceiver for (iface in remove) routings.remove(iface)?.stop() updateRoutings() @@ -34,12 +33,7 @@ class TetheringService : Service(), VpnListener.Callback { private fun updateRoutings() { active = routings.keys if (routings.isEmpty()) { - if (receiverRegistered) { - unregisterReceiver(receiver) - VpnListener.unregisterCallback(this) - upstream = null - receiverRegistered = false - } + unregisterReceiver() stopSelf() } else { val upstream = upstream @@ -61,14 +55,9 @@ class TetheringService : Service(), VpnListener.Callback { override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int { if (intent != null) { // otw service is recreated after being killed val iface = intent.getStringExtra(EXTRA_ADD_INTERFACE) - if (iface != null && VpnListener.connectivityManager.tetheredIfaces.contains(iface)) - routings.put(iface, null) + if (iface != null) routings.put(iface, null) routings.remove(intent.getStringExtra(EXTRA_REMOVE_INTERFACE))?.stop() - } else { - val active = active - if (active.isNotEmpty()) active.intersect(VpnListener.connectivityManager.tetheredIfaces.asIterable()) - .forEach { routings.put(it, null) } - } + } else active.forEach { routings.put(it, null) } updateRoutings() return START_STICKY } @@ -87,4 +76,18 @@ class TetheringService : Service(), VpnListener.Callback { routings[iface] = null } } + + override fun onDestroy() { + unregisterReceiver() + super.onDestroy() + } + + fun unregisterReceiver() { + if (receiverRegistered) { + unregisterReceiver(receiver) + VpnListener.unregisterCallback(this) + upstream = null + receiverRegistered = false + } + } }