Add Bluetooth on/off into consideration when using Bluetooth tethering
This commit is contained in:
@@ -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"/>
|
||||||
|
|||||||
@@ -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()
|
||||||
|
|||||||
@@ -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()
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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) {
|
||||||
|
|||||||
@@ -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() {
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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()) {
|
||||||
|
|||||||
@@ -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() {
|
||||||
|
|||||||
@@ -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>()
|
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user