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_WIFI_STATE"/>
<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_WIFI_STATE"/>
<uses-permission android:name="android.permission.FOREGROUND_SERVICE"/>

View File

@@ -7,6 +7,7 @@ import android.net.wifi.WifiManager
import androidx.annotation.RequiresApi
import be.mygod.vpnhotspot.App.Companion.app
import be.mygod.vpnhotspot.net.TetheringManager
import be.mygod.vpnhotspot.net.TetheringManager.localOnlyTetheredIfaces
import be.mygod.vpnhotspot.net.monitor.IpNeighbourMonitor
import be.mygod.vpnhotspot.util.StickyEvent1
import be.mygod.vpnhotspot.util.broadcastReceiver
@@ -40,7 +41,7 @@ class LocalOnlyHotspotService : IpNeighbourMonitoringService() {
private var routingManager: RoutingManager? = null
private var receiverRegistered = false
private val receiver = broadcastReceiver { _, intent ->
val ifaces = TetheringManager.getLocalOnlyTetheredIfaces(intent.extras ?: return@broadcastReceiver)
val ifaces = intent.localOnlyTetheredIfaces
DebugHelper.log(TAG, "onTetherStateChangedLocked: $ifaces")
check(ifaces.size <= 1)
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.TetherType
import be.mygod.vpnhotspot.net.TetheringManager
import be.mygod.vpnhotspot.net.TetheringManager.tetheredIfaces
import be.mygod.vpnhotspot.net.monitor.IpNeighbourMonitor
import be.mygod.vpnhotspot.util.Event0
import be.mygod.vpnhotspot.util.broadcastReceiver
@@ -45,10 +46,9 @@ class TetheringService : IpNeighbourMonitoringService() {
private val downstreams = mutableMapOf<String, Downstream>()
private var receiverRegistered = false
private val receiver = broadcastReceiver { _, intent ->
val extras = intent.extras ?: return@broadcastReceiver
synchronized(downstreams) {
val toRemove = downstreams.toMutableMap() // make a copy
for (iface in TetheringManager.getTetheredIfaces(extras)) {
for (iface in intent.tetheredIfaces) {
val downstream = toRemove.remove(iface) ?: continue
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.net.IpNeighbour
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.room.macToLong
import be.mygod.vpnhotspot.util.broadcastReceiver
@@ -18,9 +20,7 @@ import be.mygod.vpnhotspot.util.broadcastReceiver
class ClientViewModel : ViewModel(), ServiceConnection, IpNeighbourMonitor.Callback {
private var tetheredInterfaces = emptySet<String>()
private val receiver = broadcastReceiver { _, intent ->
val extras = intent.extras ?: return@broadcastReceiver
tetheredInterfaces = TetheringManager.getTetheredIfaces(extras).toSet() +
TetheringManager.getLocalOnlyTetheredIfaces(extras)
tetheredInterfaces = intent.tetheredIfaces.toSet() + intent.localOnlyTetheredIfaces
populateClients()
}

View File

@@ -1,14 +1,23 @@
package be.mygod.vpnhotspot.manage
import android.annotation.SuppressLint
import android.annotation.TargetApi
import android.bluetooth.BluetoothAdapter
import android.bluetooth.BluetoothProfile
import android.content.BroadcastReceiver
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 timber.log.Timber
class BluetoothTethering(context: Context) : BluetoothProfile.ServiceListener, AutoCloseable {
companion object {
class BluetoothTethering(context: Context, val stateListener: (Int) -> Unit) :
BluetoothProfile.ServiceListener, AutoCloseable {
companion object : BroadcastReceiver() {
/**
* PAN Profile
* From BluetoothProfile.java.
@@ -17,6 +26,42 @@ class BluetoothTethering(context: Context) : BluetoothProfile.ServiceListener, A
private val isTetheringOn by lazy @SuppressLint("PrivateApi") {
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
@@ -29,6 +74,8 @@ class BluetoothTethering(context: Context) : BluetoothProfile.ServiceListener, A
isTetheringOn.invoke(pan) as Boolean
}
private val receiver = broadcastReceiver { _, intent -> stateListener(intent.bluetoothState) }
init {
try {
BluetoothAdapter.getDefaultAdapter()?.getProfileProxy(context, this, PAN)
@@ -36,6 +83,7 @@ class BluetoothTethering(context: Context) : BluetoothProfile.ServiceListener, A
Timber.w(e)
SmartSnackbar.make(e).show()
}
registerBluetoothStateListener(receiver)
}
override fun onServiceDisconnected(profile: Int) {

View File

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

View File

@@ -149,7 +149,7 @@ sealed class TetherManager(protected val parent: TetheringFragment) : Manager(),
}
@RequiresApi(24)
class Bluetooth(parent: TetheringFragment) : TetherManager(parent), LifecycleObserver {
private val tethering = BluetoothTethering(parent.requireContext())
private val tethering = BluetoothTethering(parent.requireContext()) { onTetheringStarted() }
init {
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 isStarted get() = tethering.active == true
override fun start() = TetheringManager.start(TetheringManager.TETHERING_BLUETOOTH, true, this)
override fun start() = BluetoothTethering.start(this)
override fun stop() {
TetheringManager.stop(TetheringManager.TETHERING_BLUETOOTH)
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.net.TetherType
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.broadcastReceiver
import kotlinx.android.synthetic.main.activity_main.*
@@ -88,10 +90,8 @@ class TetheringFragment : Fragment(), ServiceConnection, MenuItem.OnMenuItemClic
var binder: TetheringService.Binder? = null
private val adapter = ManagerAdapter()
private val receiver = broadcastReceiver { _, intent ->
val extras = intent.extras ?: return@broadcastReceiver
adapter.update(TetheringManager.getTetheredIfaces(extras),
TetheringManager.getLocalOnlyTetheredIfaces(extras),
extras.getStringArrayList(TetheringManager.EXTRA_ERRORED_TETHER)!!)
adapter.update(intent.tetheredIfaces, intent.localOnlyTetheredIfaces,
intent.getStringArrayListExtra(TetheringManager.EXTRA_ERRORED_TETHER))
}
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 tetherType get() = TetherType.BLUETOOTH
override fun start() = TetheringManager.start(TetheringManager.TETHERING_BLUETOOTH, true, this)
override fun start() = BluetoothTethering.start(this)
override fun stop() {
TetheringManager.stop(TetheringManager.TETHERING_BLUETOOTH)
Thread.sleep(1) // give others a room to breathe
@@ -135,7 +135,7 @@ sealed class TetheringTileService : TetherListeningTileService(), TetheringManag
}
override fun onStartListening() {
tethering = BluetoothTethering(this)
tethering = BluetoothTethering(this) { updateTile() }
super.onStartListening()
}
override fun onStopListening() {

View File

@@ -1,9 +1,9 @@
package be.mygod.vpnhotspot.net
import android.annotation.SuppressLint
import android.content.Intent
import android.net.ConnectivityManager
import android.os.Build
import android.os.Bundle
import android.os.Handler
import androidx.annotation.RequiresApi
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 getTetheredIfaces(extras: Bundle) = extras.getStringArrayList(
if (Build.VERSION.SDK_INT >= 26) EXTRA_ACTIVE_TETHER else EXTRA_ACTIVE_TETHER_LEGACY)!!
fun getLocalOnlyTetheredIfaces(extras: Bundle) =
if (Build.VERSION.SDK_INT >= 26) extras.getStringArrayList(EXTRA_ACTIVE_LOCAL_ONLY)!!
else emptyList<String>()
val Intent.tetheredIfaces get() = getStringArrayListExtra(
if (Build.VERSION.SDK_INT >= 26) EXTRA_ACTIVE_TETHER else EXTRA_ACTIVE_TETHER_LEGACY)
val Intent.localOnlyTetheredIfaces get() =
if (Build.VERSION.SDK_INT >= 26) getStringArrayListExtra(EXTRA_ACTIVE_LOCAL_ONLY) else emptyList<String>()
}