Add Bluetooth on/off into consideration when using Bluetooth tethering

This commit is contained in:
Mygod
2019-03-18 19:02:50 +08:00
parent 621b6eac74
commit e112b10c55
10 changed files with 75 additions and 25 deletions

View File

@@ -25,6 +25,7 @@
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/> <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/>
<uses-permission android:name="android.permission.ACCESS_WIFI_STATE"/> <uses-permission android:name="android.permission.ACCESS_WIFI_STATE"/>
<uses-permission android:name="android.permission.BLUETOOTH"/> <uses-permission android:name="android.permission.BLUETOOTH"/>
<uses-permission android:name="android.permission.BLUETOOTH_ADMIN"/>
<uses-permission android:name="android.permission.CHANGE_NETWORK_STATE"/> <uses-permission android:name="android.permission.CHANGE_NETWORK_STATE"/>
<uses-permission android:name="android.permission.CHANGE_WIFI_STATE"/> <uses-permission android:name="android.permission.CHANGE_WIFI_STATE"/>
<uses-permission android:name="android.permission.FOREGROUND_SERVICE"/> <uses-permission android:name="android.permission.FOREGROUND_SERVICE"/>

View File

@@ -7,6 +7,7 @@ import android.net.wifi.WifiManager
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.TetheringManager import be.mygod.vpnhotspot.net.TetheringManager
import be.mygod.vpnhotspot.net.TetheringManager.localOnlyTetheredIfaces
import be.mygod.vpnhotspot.net.monitor.IpNeighbourMonitor import be.mygod.vpnhotspot.net.monitor.IpNeighbourMonitor
import be.mygod.vpnhotspot.util.StickyEvent1 import be.mygod.vpnhotspot.util.StickyEvent1
import be.mygod.vpnhotspot.util.broadcastReceiver import be.mygod.vpnhotspot.util.broadcastReceiver
@@ -40,7 +41,7 @@ class LocalOnlyHotspotService : IpNeighbourMonitoringService() {
private var routingManager: RoutingManager? = null private var routingManager: RoutingManager? = null
private var receiverRegistered = false private var receiverRegistered = false
private val receiver = broadcastReceiver { _, intent -> private val receiver = broadcastReceiver { _, intent ->
val ifaces = TetheringManager.getLocalOnlyTetheredIfaces(intent.extras ?: return@broadcastReceiver) val ifaces = intent.localOnlyTetheredIfaces
DebugHelper.log(TAG, "onTetherStateChangedLocked: $ifaces") DebugHelper.log(TAG, "onTetherStateChangedLocked: $ifaces")
check(ifaces.size <= 1) check(ifaces.size <= 1)
val iface = ifaces.singleOrNull() val iface = ifaces.singleOrNull()

View File

@@ -6,6 +6,7 @@ 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.TetherType
import be.mygod.vpnhotspot.net.TetheringManager import be.mygod.vpnhotspot.net.TetheringManager
import be.mygod.vpnhotspot.net.TetheringManager.tetheredIfaces
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.util.broadcastReceiver import be.mygod.vpnhotspot.util.broadcastReceiver
@@ -45,10 +46,9 @@ class TetheringService : IpNeighbourMonitoringService() {
private val downstreams = mutableMapOf<String, Downstream>() private val downstreams = mutableMapOf<String, Downstream>()
private var receiverRegistered = false private var receiverRegistered = false
private val receiver = broadcastReceiver { _, intent -> private val receiver = broadcastReceiver { _, intent ->
val extras = intent.extras ?: return@broadcastReceiver
synchronized(downstreams) { synchronized(downstreams) {
val toRemove = downstreams.toMutableMap() // make a copy val toRemove = downstreams.toMutableMap() // make a copy
for (iface in TetheringManager.getTetheredIfaces(extras)) { for (iface in intent.tetheredIfaces) {
val downstream = toRemove.remove(iface) ?: continue val downstream = toRemove.remove(iface) ?: continue
if (downstream.monitor) downstream.start() if (downstream.monitor) downstream.start()
} }

View File

@@ -11,6 +11,8 @@ import be.mygod.vpnhotspot.App.Companion.app
import be.mygod.vpnhotspot.RepeaterService import be.mygod.vpnhotspot.RepeaterService
import be.mygod.vpnhotspot.net.IpNeighbour import be.mygod.vpnhotspot.net.IpNeighbour
import be.mygod.vpnhotspot.net.TetheringManager import be.mygod.vpnhotspot.net.TetheringManager
import be.mygod.vpnhotspot.net.TetheringManager.localOnlyTetheredIfaces
import be.mygod.vpnhotspot.net.TetheringManager.tetheredIfaces
import be.mygod.vpnhotspot.net.monitor.IpNeighbourMonitor import be.mygod.vpnhotspot.net.monitor.IpNeighbourMonitor
import be.mygod.vpnhotspot.room.macToLong import be.mygod.vpnhotspot.room.macToLong
import be.mygod.vpnhotspot.util.broadcastReceiver import be.mygod.vpnhotspot.util.broadcastReceiver
@@ -18,9 +20,7 @@ import be.mygod.vpnhotspot.util.broadcastReceiver
class ClientViewModel : ViewModel(), ServiceConnection, IpNeighbourMonitor.Callback { class ClientViewModel : ViewModel(), ServiceConnection, IpNeighbourMonitor.Callback {
private var tetheredInterfaces = emptySet<String>() private var tetheredInterfaces = emptySet<String>()
private val receiver = broadcastReceiver { _, intent -> private val receiver = broadcastReceiver { _, intent ->
val extras = intent.extras ?: return@broadcastReceiver tetheredInterfaces = intent.tetheredIfaces.toSet() + intent.localOnlyTetheredIfaces
tetheredInterfaces = TetheringManager.getTetheredIfaces(extras).toSet() +
TetheringManager.getLocalOnlyTetheredIfaces(extras)
populateClients() populateClients()
} }

View File

@@ -1,14 +1,23 @@
package be.mygod.vpnhotspot.manage package be.mygod.vpnhotspot.manage
import android.annotation.SuppressLint import android.annotation.SuppressLint
import android.annotation.TargetApi
import android.bluetooth.BluetoothAdapter import android.bluetooth.BluetoothAdapter
import android.bluetooth.BluetoothProfile import android.bluetooth.BluetoothProfile
import android.content.BroadcastReceiver
import android.content.Context import android.content.Context
import android.content.Intent
import android.content.IntentFilter
import androidx.annotation.RequiresApi
import be.mygod.vpnhotspot.App.Companion.app
import be.mygod.vpnhotspot.net.TetheringManager
import be.mygod.vpnhotspot.util.broadcastReceiver
import be.mygod.vpnhotspot.widget.SmartSnackbar import be.mygod.vpnhotspot.widget.SmartSnackbar
import timber.log.Timber import timber.log.Timber
class BluetoothTethering(context: Context) : BluetoothProfile.ServiceListener, AutoCloseable { class BluetoothTethering(context: Context, val stateListener: (Int) -> Unit) :
companion object { BluetoothProfile.ServiceListener, AutoCloseable {
companion object : BroadcastReceiver() {
/** /**
* PAN Profile * PAN Profile
* From BluetoothProfile.java. * From BluetoothProfile.java.
@@ -17,6 +26,42 @@ class BluetoothTethering(context: Context) : BluetoothProfile.ServiceListener, A
private val isTetheringOn by lazy @SuppressLint("PrivateApi") { private val isTetheringOn by lazy @SuppressLint("PrivateApi") {
Class.forName("android.bluetooth.BluetoothPan").getDeclaredMethod("isTetheringOn") Class.forName("android.bluetooth.BluetoothPan").getDeclaredMethod("isTetheringOn")
} }
private fun registerBluetoothStateListener(receiver: BroadcastReceiver) =
app.registerReceiver(receiver, IntentFilter(BluetoothAdapter.ACTION_STATE_CHANGED))
private val Intent.bluetoothState get() = getIntExtra(BluetoothAdapter.EXTRA_STATE, BluetoothAdapter.ERROR)
private var pendingCallback: TetheringManager.OnStartTetheringCallback? = null
/**
* https://android.googlesource.com/platform/packages/apps/Settings/+/b1af85d/src/com/android/settings/TetherSettings.java#215
*/
@TargetApi(24)
override fun onReceive(context: Context?, intent: Intent?) {
when (intent?.bluetoothState) {
BluetoothAdapter.STATE_ON -> {
TetheringManager.start(TetheringManager.TETHERING_BLUETOOTH, true, pendingCallback!!)
}
BluetoothAdapter.STATE_OFF, BluetoothAdapter.ERROR -> { }
else -> return // ignore transition states
}
pendingCallback = null
app.unregisterReceiver(this)
}
/**
* https://android.googlesource.com/platform/packages/apps/Settings/+/b1af85d/src/com/android/settings/TetherSettings.java#384
*/
@RequiresApi(24)
fun start(callback: TetheringManager.OnStartTetheringCallback) {
if (pendingCallback != null) return
val adapter = BluetoothAdapter.getDefaultAdapter()
if (adapter?.state == BluetoothAdapter.STATE_OFF) {
registerBluetoothStateListener(this)
pendingCallback = callback
adapter.enable()
} else TetheringManager.start(TetheringManager.TETHERING_BLUETOOTH, true, callback)
}
} }
private var pan: BluetoothProfile? = null private var pan: BluetoothProfile? = null
@@ -29,6 +74,8 @@ class BluetoothTethering(context: Context) : BluetoothProfile.ServiceListener, A
isTetheringOn.invoke(pan) as Boolean isTetheringOn.invoke(pan) as Boolean
} }
private val receiver = broadcastReceiver { _, intent -> stateListener(intent.bluetoothState) }
init { init {
try { try {
BluetoothAdapter.getDefaultAdapter()?.getProfileProxy(context, this, PAN) BluetoothAdapter.getDefaultAdapter()?.getProfileProxy(context, this, PAN)
@@ -36,6 +83,7 @@ class BluetoothTethering(context: Context) : BluetoothProfile.ServiceListener, A
Timber.w(e) Timber.w(e)
SmartSnackbar.make(e).show() SmartSnackbar.make(e).show()
} }
registerBluetoothStateListener(receiver)
} }
override fun onServiceDisconnected(profile: Int) { override fun onServiceDisconnected(profile: Int) {

View File

@@ -3,6 +3,7 @@ package be.mygod.vpnhotspot.manage
import android.content.IntentFilter import android.content.IntentFilter
import androidx.annotation.RequiresApi import androidx.annotation.RequiresApi
import be.mygod.vpnhotspot.net.TetheringManager import be.mygod.vpnhotspot.net.TetheringManager
import be.mygod.vpnhotspot.net.TetheringManager.tetheredIfaces
import be.mygod.vpnhotspot.util.KillableTileService import be.mygod.vpnhotspot.util.KillableTileService
import be.mygod.vpnhotspot.util.broadcastReceiver import be.mygod.vpnhotspot.util.broadcastReceiver
@@ -11,14 +12,14 @@ abstract class TetherListeningTileService : KillableTileService() {
protected var tethered: List<String> = emptyList() protected var tethered: List<String> = emptyList()
private val receiver = broadcastReceiver { _, intent -> private val receiver = broadcastReceiver { _, intent ->
tethered = TetheringManager.getTetheredIfaces(intent.extras ?: return@broadcastReceiver) tethered = intent.tetheredIfaces
updateTile() updateTile()
} }
override fun onStartListening() { override fun onStartListening() {
super.onStartListening() super.onStartListening()
tethered = TetheringManager.getTetheredIfaces(registerReceiver( val intent = registerReceiver(receiver, IntentFilter(TetheringManager.ACTION_TETHER_STATE_CHANGED))
receiver, IntentFilter(TetheringManager.ACTION_TETHER_STATE_CHANGED))?.extras ?: return) if (intent != null) tethered = intent.tetheredIfaces
} }
override fun onStopListening() { override fun onStopListening() {

View File

@@ -149,7 +149,7 @@ sealed class TetherManager(protected val parent: TetheringFragment) : Manager(),
} }
@RequiresApi(24) @RequiresApi(24)
class Bluetooth(parent: TetheringFragment) : TetherManager(parent), LifecycleObserver { class Bluetooth(parent: TetheringFragment) : TetherManager(parent), LifecycleObserver {
private val tethering = BluetoothTethering(parent.requireContext()) private val tethering = BluetoothTethering(parent.requireContext()) { onTetheringStarted() }
init { init {
parent.lifecycle.addObserver(this) parent.lifecycle.addObserver(this)
@@ -163,7 +163,7 @@ sealed class TetherManager(protected val parent: TetheringFragment) : Manager(),
override val type get() = VIEW_TYPE_BLUETOOTH override val type get() = VIEW_TYPE_BLUETOOTH
override val isStarted get() = tethering.active == true override val isStarted get() = tethering.active == true
override fun start() = TetheringManager.start(TetheringManager.TETHERING_BLUETOOTH, true, this) override fun start() = BluetoothTethering.start(this)
override fun stop() { override fun stop() {
TetheringManager.stop(TetheringManager.TETHERING_BLUETOOTH) TetheringManager.stop(TetheringManager.TETHERING_BLUETOOTH)
Thread.sleep(1) // give others a room to breathe Thread.sleep(1) // give others a room to breathe

View File

@@ -21,6 +21,8 @@ import be.mygod.vpnhotspot.*
import be.mygod.vpnhotspot.databinding.FragmentTetheringBinding import be.mygod.vpnhotspot.databinding.FragmentTetheringBinding
import be.mygod.vpnhotspot.net.TetherType import be.mygod.vpnhotspot.net.TetherType
import be.mygod.vpnhotspot.net.TetheringManager import be.mygod.vpnhotspot.net.TetheringManager
import be.mygod.vpnhotspot.net.TetheringManager.localOnlyTetheredIfaces
import be.mygod.vpnhotspot.net.TetheringManager.tetheredIfaces
import be.mygod.vpnhotspot.util.ServiceForegroundConnector import be.mygod.vpnhotspot.util.ServiceForegroundConnector
import be.mygod.vpnhotspot.util.broadcastReceiver import be.mygod.vpnhotspot.util.broadcastReceiver
import kotlinx.android.synthetic.main.activity_main.* import kotlinx.android.synthetic.main.activity_main.*
@@ -88,10 +90,8 @@ class TetheringFragment : Fragment(), ServiceConnection, MenuItem.OnMenuItemClic
var binder: TetheringService.Binder? = null var binder: TetheringService.Binder? = null
private val adapter = ManagerAdapter() private val adapter = ManagerAdapter()
private val receiver = broadcastReceiver { _, intent -> private val receiver = broadcastReceiver { _, intent ->
val extras = intent.extras ?: return@broadcastReceiver adapter.update(intent.tetheredIfaces, intent.localOnlyTetheredIfaces,
adapter.update(TetheringManager.getTetheredIfaces(extras), intent.getStringArrayListExtra(TetheringManager.EXTRA_ERRORED_TETHER))
TetheringManager.getLocalOnlyTetheredIfaces(extras),
extras.getStringArrayList(TetheringManager.EXTRA_ERRORED_TETHER)!!)
} }
private fun updateMonitorList(canMonitor: List<String> = emptyList()) { private fun updateMonitorList(canMonitor: List<String> = emptyList()) {

View File

@@ -127,7 +127,7 @@ sealed class TetheringTileService : TetherListeningTileService(), TetheringManag
override val labelString get() = R.string.tethering_manage_bluetooth override val labelString get() = R.string.tethering_manage_bluetooth
override val tetherType get() = TetherType.BLUETOOTH override val tetherType get() = TetherType.BLUETOOTH
override fun start() = TetheringManager.start(TetheringManager.TETHERING_BLUETOOTH, true, this) override fun start() = BluetoothTethering.start(this)
override fun stop() { override fun stop() {
TetheringManager.stop(TetheringManager.TETHERING_BLUETOOTH) TetheringManager.stop(TetheringManager.TETHERING_BLUETOOTH)
Thread.sleep(1) // give others a room to breathe Thread.sleep(1) // give others a room to breathe
@@ -135,7 +135,7 @@ sealed class TetheringTileService : TetherListeningTileService(), TetheringManag
} }
override fun onStartListening() { override fun onStartListening() {
tethering = BluetoothTethering(this) tethering = BluetoothTethering(this) { updateTile() }
super.onStartListening() super.onStartListening()
} }
override fun onStopListening() { override fun onStopListening() {

View File

@@ -1,9 +1,9 @@
package be.mygod.vpnhotspot.net package be.mygod.vpnhotspot.net
import android.annotation.SuppressLint import android.annotation.SuppressLint
import android.content.Intent
import android.net.ConnectivityManager import android.net.ConnectivityManager
import android.os.Build import android.os.Build
import android.os.Bundle
import android.os.Handler import android.os.Handler
import androidx.annotation.RequiresApi import androidx.annotation.RequiresApi
import be.mygod.vpnhotspot.App.Companion.app import be.mygod.vpnhotspot.App.Companion.app
@@ -165,9 +165,8 @@ object TetheringManager {
*/ */
fun getLastTetherError(iface: String): Int = getLastTetherError.invoke(app.connectivity, iface) as Int fun getLastTetherError(iface: String): Int = getLastTetherError.invoke(app.connectivity, iface) as Int
fun getTetheredIfaces(extras: Bundle) = extras.getStringArrayList( val Intent.tetheredIfaces get() = getStringArrayListExtra(
if (Build.VERSION.SDK_INT >= 26) EXTRA_ACTIVE_TETHER else EXTRA_ACTIVE_TETHER_LEGACY)!! if (Build.VERSION.SDK_INT >= 26) EXTRA_ACTIVE_TETHER else EXTRA_ACTIVE_TETHER_LEGACY)
fun getLocalOnlyTetheredIfaces(extras: Bundle) = val Intent.localOnlyTetheredIfaces get() =
if (Build.VERSION.SDK_INT >= 26) extras.getStringArrayList(EXTRA_ACTIVE_LOCAL_ONLY)!! if (Build.VERSION.SDK_INT >= 26) getStringArrayListExtra(EXTRA_ACTIVE_LOCAL_ONLY) else emptyList<String>()
else emptyList<String>()
} }