From e112b10c558ac27fda89546b219e1c561dde2a76 Mon Sep 17 00:00:00 2001 From: Mygod Date: Mon, 18 Mar 2019 19:02:50 +0800 Subject: [PATCH] Add Bluetooth on/off into consideration when using Bluetooth tethering --- mobile/src/main/AndroidManifest.xml | 1 + .../vpnhotspot/LocalOnlyHotspotService.kt | 3 +- .../be/mygod/vpnhotspot/TetheringService.kt | 4 +- .../vpnhotspot/client/ClientViewModel.kt | 6 +-- .../vpnhotspot/manage/BluetoothTethering.kt | 52 ++++++++++++++++++- .../manage/TetherListeningTileService.kt | 7 +-- .../mygod/vpnhotspot/manage/TetherManager.kt | 4 +- .../vpnhotspot/manage/TetheringFragment.kt | 8 +-- .../vpnhotspot/manage/TetheringTileService.kt | 4 +- .../mygod/vpnhotspot/net/TetheringManager.kt | 11 ++-- 10 files changed, 75 insertions(+), 25 deletions(-) diff --git a/mobile/src/main/AndroidManifest.xml b/mobile/src/main/AndroidManifest.xml index ebff9bde..293052ae 100644 --- a/mobile/src/main/AndroidManifest.xml +++ b/mobile/src/main/AndroidManifest.xml @@ -25,6 +25,7 @@ + diff --git a/mobile/src/main/java/be/mygod/vpnhotspot/LocalOnlyHotspotService.kt b/mobile/src/main/java/be/mygod/vpnhotspot/LocalOnlyHotspotService.kt index 22fe8ec3..fe7cb571 100644 --- a/mobile/src/main/java/be/mygod/vpnhotspot/LocalOnlyHotspotService.kt +++ b/mobile/src/main/java/be/mygod/vpnhotspot/LocalOnlyHotspotService.kt @@ -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() diff --git a/mobile/src/main/java/be/mygod/vpnhotspot/TetheringService.kt b/mobile/src/main/java/be/mygod/vpnhotspot/TetheringService.kt index 21dbb456..79af6088 100644 --- a/mobile/src/main/java/be/mygod/vpnhotspot/TetheringService.kt +++ b/mobile/src/main/java/be/mygod/vpnhotspot/TetheringService.kt @@ -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() 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() } diff --git a/mobile/src/main/java/be/mygod/vpnhotspot/client/ClientViewModel.kt b/mobile/src/main/java/be/mygod/vpnhotspot/client/ClientViewModel.kt index 89dacc44..7983ea25 100644 --- a/mobile/src/main/java/be/mygod/vpnhotspot/client/ClientViewModel.kt +++ b/mobile/src/main/java/be/mygod/vpnhotspot/client/ClientViewModel.kt @@ -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() 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() } diff --git a/mobile/src/main/java/be/mygod/vpnhotspot/manage/BluetoothTethering.kt b/mobile/src/main/java/be/mygod/vpnhotspot/manage/BluetoothTethering.kt index 33901b60..520b0092 100644 --- a/mobile/src/main/java/be/mygod/vpnhotspot/manage/BluetoothTethering.kt +++ b/mobile/src/main/java/be/mygod/vpnhotspot/manage/BluetoothTethering.kt @@ -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) { diff --git a/mobile/src/main/java/be/mygod/vpnhotspot/manage/TetherListeningTileService.kt b/mobile/src/main/java/be/mygod/vpnhotspot/manage/TetherListeningTileService.kt index 1d533938..0917b7e0 100644 --- a/mobile/src/main/java/be/mygod/vpnhotspot/manage/TetherListeningTileService.kt +++ b/mobile/src/main/java/be/mygod/vpnhotspot/manage/TetherListeningTileService.kt @@ -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 = 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() { diff --git a/mobile/src/main/java/be/mygod/vpnhotspot/manage/TetherManager.kt b/mobile/src/main/java/be/mygod/vpnhotspot/manage/TetherManager.kt index 1a0c3be4..bdfa5414 100644 --- a/mobile/src/main/java/be/mygod/vpnhotspot/manage/TetherManager.kt +++ b/mobile/src/main/java/be/mygod/vpnhotspot/manage/TetherManager.kt @@ -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 diff --git a/mobile/src/main/java/be/mygod/vpnhotspot/manage/TetheringFragment.kt b/mobile/src/main/java/be/mygod/vpnhotspot/manage/TetheringFragment.kt index e868318e..9f859cd7 100644 --- a/mobile/src/main/java/be/mygod/vpnhotspot/manage/TetheringFragment.kt +++ b/mobile/src/main/java/be/mygod/vpnhotspot/manage/TetheringFragment.kt @@ -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 = emptyList()) { diff --git a/mobile/src/main/java/be/mygod/vpnhotspot/manage/TetheringTileService.kt b/mobile/src/main/java/be/mygod/vpnhotspot/manage/TetheringTileService.kt index 371ff8f7..143cea53 100644 --- a/mobile/src/main/java/be/mygod/vpnhotspot/manage/TetheringTileService.kt +++ b/mobile/src/main/java/be/mygod/vpnhotspot/manage/TetheringTileService.kt @@ -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() { diff --git a/mobile/src/main/java/be/mygod/vpnhotspot/net/TetheringManager.kt b/mobile/src/main/java/be/mygod/vpnhotspot/net/TetheringManager.kt index 3d1448b6..68a7a905 100644 --- a/mobile/src/main/java/be/mygod/vpnhotspot/net/TetheringManager.kt +++ b/mobile/src/main/java/be/mygod/vpnhotspot/net/TetheringManager.kt @@ -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() + 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() }