From 71f466913a400fc9ecf9045bb5065861e47a6a0e Mon Sep 17 00:00:00 2001 From: Mygod Date: Wed, 19 May 2021 14:55:34 -0400 Subject: [PATCH] Support BLUETOOTH_CONNECT permission on Android 12 beta --- mobile/src/main/AndroidManifest.xml | 2 ++ .../vpnhotspot/manage/BluetoothTethering.kt | 22 +++++++++++++------ .../mygod/vpnhotspot/manage/TetherManager.kt | 20 +++++++++++++++++ .../vpnhotspot/manage/TetheringFragment.kt | 7 +++++- .../vpnhotspot/manage/TetheringTileService.kt | 6 ++++- 5 files changed, 48 insertions(+), 9 deletions(-) diff --git a/mobile/src/main/AndroidManifest.xml b/mobile/src/main/AndroidManifest.xml index bc131135..c16a50a7 100644 --- a/mobile/src/main/AndroidManifest.xml +++ b/mobile/src/main/AndroidManifest.xml @@ -52,6 +52,8 @@ + + Unit) : fun start(callback: TetheringManager.StartTetheringCallback) { if (pendingCallback != null) return val adapter = BluetoothAdapter.getDefaultAdapter() - if (adapter?.state == BluetoothAdapter.STATE_OFF) { - registerBluetoothStateListener(this) - pendingCallback = callback - adapter.enable() - } else TetheringManager.startTethering(TetheringManager.TETHERING_BLUETOOTH, true, callback) + try { + if (adapter?.state == BluetoothAdapter.STATE_OFF) { + registerBluetoothStateListener(this) + pendingCallback = callback + adapter.enable() + } else TetheringManager.startTethering(TetheringManager.TETHERING_BLUETOOTH, true, callback) + } catch (e: SecurityException) { + SmartSnackbar.make(e.readableMessage).shortToast().show() + } } } @@ -94,13 +99,16 @@ class BluetoothTethering(context: Context, val stateListener: () -> Unit) : private val receiver = broadcastReceiver { _, _ -> stateListener() } - init { - if (BluetoothAdapter.getDefaultAdapter() != null) try { + fun ensureInit(context: Context) { + if (pan == null && BluetoothAdapter.getDefaultAdapter() != null) try { pan = pan(context, this) } catch (e: InvocationTargetException) { Timber.w(e) activeFailureCause = e } + } + init { + ensureInit(context) registerBluetoothStateListener(receiver) } 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 7036a4c8..36b86630 100644 --- a/mobile/src/main/java/be/mygod/vpnhotspot/manage/TetherManager.kt +++ b/mobile/src/main/java/be/mygod/vpnhotspot/manage/TetherManager.kt @@ -1,8 +1,11 @@ package be.mygod.vpnhotspot.manage +import android.Manifest import android.annotation.TargetApi import android.content.ClipData +import android.content.Context import android.content.Intent +import android.content.pm.PackageManager import android.net.MacAddress import android.os.Build import android.provider.Settings @@ -10,11 +13,14 @@ import android.view.View import android.widget.Toast import androidx.annotation.RequiresApi import androidx.core.net.toUri +import androidx.core.os.BuildCompat import androidx.core.view.updatePaddingRelative +import androidx.fragment.app.Fragment import androidx.lifecycle.DefaultLifecycleObserver import androidx.lifecycle.LifecycleOwner import androidx.recyclerview.widget.RecyclerView import be.mygod.vpnhotspot.App.Companion.app +import be.mygod.vpnhotspot.BuildConfig import be.mygod.vpnhotspot.MainActivity import be.mygod.vpnhotspot.R import be.mygod.vpnhotspot.databinding.ListitemInterfaceBinding @@ -215,12 +221,26 @@ sealed class TetherManager(protected val parent: TetheringFragment) : Manager(), } @RequiresApi(24) class Bluetooth(parent: TetheringFragment) : TetherManager(parent), DefaultLifecycleObserver { + companion object { + // TODO: migrate to framework Manifest.permission when stable + private const val BLUETOOTH_CONNECT = "android.permission.BLUETOOTH_CONNECT" + } + private val tethering = BluetoothTethering(parent.requireContext()) { data.notifyChange() } init { parent.viewLifecycleOwner.lifecycle.addObserver(this) } + fun ensureInit(context: Context) = tethering.ensureInit(context) + override fun onResume(owner: LifecycleOwner) { + if (!BuildCompat.isAtLeastS() || parent.requireContext().checkSelfPermission(BLUETOOTH_CONNECT) == + PackageManager.PERMISSION_GRANTED) { + ensureInit(parent.requireContext()) + } else if (parent.shouldShowRequestPermissionRationale(BLUETOOTH_CONNECT)) { + parent.requestBluetooth.launch(BLUETOOTH_CONNECT) + } + } override fun onDestroy(owner: LifecycleOwner) = tethering.close() override val title get() = parent.getString(R.string.tethering_manage_bluetooth) 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 ace3cc2b..7284ac35 100644 --- a/mobile/src/main/java/be/mygod/vpnhotspot/manage/TetheringFragment.kt +++ b/mobile/src/main/java/be/mygod/vpnhotspot/manage/TetheringFragment.kt @@ -46,11 +46,12 @@ class TetheringFragment : Fragment(), ServiceConnection, Toolbar.OnMenuItemClick internal val repeaterManager by lazy { RepeaterManager(this@TetheringFragment) } @get:RequiresApi(26) internal val localOnlyHotspotManager by lazy @TargetApi(26) { LocalOnlyHotspotManager(this@TetheringFragment) } + internal val bluetoothManager by lazy @TargetApi(24) { TetherManager.Bluetooth(this@TetheringFragment) } @get:RequiresApi(24) private val tetherManagers by lazy @TargetApi(24) { listOf(TetherManager.Wifi(this@TetheringFragment), TetherManager.Usb(this@TetheringFragment), - TetherManager.Bluetooth(this@TetheringFragment)) + bluetoothManager) } @get:RequiresApi(30) private val tetherManagers30 by lazy @TargetApi(30) { @@ -130,6 +131,10 @@ class TetheringFragment : Fragment(), ServiceConnection, Toolbar.OnMenuItemClick val startLocalOnlyHotspot = registerForActivityResult(ActivityResultContracts.RequestPermission()) { adapter.localOnlyHotspotManager.start(requireContext()) } + @RequiresApi(31) + val requestBluetooth = registerForActivityResult(ActivityResultContracts.RequestPermission()) { granted -> + if (granted) adapter.bluetoothManager.ensureInit(requireContext()) + } var ifaceLookup: Map = emptyMap() var enabledTypes = emptySet() 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 5a57f4ea..6483beea 100644 --- a/mobile/src/main/java/be/mygod/vpnhotspot/manage/TetheringTileService.kt +++ b/mobile/src/main/java/be/mygod/vpnhotspot/manage/TetheringTileService.kt @@ -186,7 +186,11 @@ sealed class TetheringTileService : IpNeighbourMonitoringTileService(), Tetherin state = Tile.STATE_INACTIVE icon = tileOff } - null -> return + null -> { + state = Tile.STATE_UNAVAILABLE + icon = tileOff + subtitle(tethering?.activeFailureCause?.readableMessage) + } } label = getText(labelString) updateTile()