Support BLUETOOTH_CONNECT permission on Android 12 beta

This commit is contained in:
Mygod
2021-05-19 14:55:34 -04:00
parent e679974796
commit 71f466913a
5 changed files with 48 additions and 9 deletions

View File

@@ -52,6 +52,8 @@
<uses-permission-sdk-23 android:name="android.permission.ACCESS_COARSE_LOCATION"/> <uses-permission-sdk-23 android:name="android.permission.ACCESS_COARSE_LOCATION"/>
<!-- Required since API 29 --> <!-- Required since API 29 -->
<uses-permission-sdk-23 android:name="android.permission.ACCESS_FINE_LOCATION"/> <uses-permission-sdk-23 android:name="android.permission.ACCESS_FINE_LOCATION"/>
<!-- Required since API 31 -->
<uses-permission-sdk-23 android:name="android.permission.BLUETOOTH_CONNECT"/>
<application <application
android:name=".App" android:name=".App"

View File

@@ -13,6 +13,7 @@ import be.mygod.vpnhotspot.App.Companion.app
import be.mygod.vpnhotspot.net.TetheringManager import be.mygod.vpnhotspot.net.TetheringManager
import be.mygod.vpnhotspot.util.broadcastReceiver import be.mygod.vpnhotspot.util.broadcastReceiver
import be.mygod.vpnhotspot.util.readableMessage import be.mygod.vpnhotspot.util.readableMessage
import be.mygod.vpnhotspot.widget.SmartSnackbar
import timber.log.Timber import timber.log.Timber
import java.lang.reflect.InvocationTargetException import java.lang.reflect.InvocationTargetException
@@ -64,11 +65,15 @@ class BluetoothTethering(context: Context, val stateListener: () -> Unit) :
fun start(callback: TetheringManager.StartTetheringCallback) { fun start(callback: TetheringManager.StartTetheringCallback) {
if (pendingCallback != null) return if (pendingCallback != null) return
val adapter = BluetoothAdapter.getDefaultAdapter() val adapter = BluetoothAdapter.getDefaultAdapter()
try {
if (adapter?.state == BluetoothAdapter.STATE_OFF) { if (adapter?.state == BluetoothAdapter.STATE_OFF) {
registerBluetoothStateListener(this) registerBluetoothStateListener(this)
pendingCallback = callback pendingCallback = callback
adapter.enable() adapter.enable()
} else TetheringManager.startTethering(TetheringManager.TETHERING_BLUETOOTH, true, callback) } 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() } private val receiver = broadcastReceiver { _, _ -> stateListener() }
init { fun ensureInit(context: Context) {
if (BluetoothAdapter.getDefaultAdapter() != null) try { if (pan == null && BluetoothAdapter.getDefaultAdapter() != null) try {
pan = pan(context, this) pan = pan(context, this)
} catch (e: InvocationTargetException) { } catch (e: InvocationTargetException) {
Timber.w(e) Timber.w(e)
activeFailureCause = e activeFailureCause = e
} }
}
init {
ensureInit(context)
registerBluetoothStateListener(receiver) registerBluetoothStateListener(receiver)
} }

View File

@@ -1,8 +1,11 @@
package be.mygod.vpnhotspot.manage package be.mygod.vpnhotspot.manage
import android.Manifest
import android.annotation.TargetApi import android.annotation.TargetApi
import android.content.ClipData import android.content.ClipData
import android.content.Context
import android.content.Intent import android.content.Intent
import android.content.pm.PackageManager
import android.net.MacAddress import android.net.MacAddress
import android.os.Build import android.os.Build
import android.provider.Settings import android.provider.Settings
@@ -10,11 +13,14 @@ import android.view.View
import android.widget.Toast import android.widget.Toast
import androidx.annotation.RequiresApi import androidx.annotation.RequiresApi
import androidx.core.net.toUri import androidx.core.net.toUri
import androidx.core.os.BuildCompat
import androidx.core.view.updatePaddingRelative import androidx.core.view.updatePaddingRelative
import androidx.fragment.app.Fragment
import androidx.lifecycle.DefaultLifecycleObserver import androidx.lifecycle.DefaultLifecycleObserver
import androidx.lifecycle.LifecycleOwner import androidx.lifecycle.LifecycleOwner
import androidx.recyclerview.widget.RecyclerView import androidx.recyclerview.widget.RecyclerView
import be.mygod.vpnhotspot.App.Companion.app import be.mygod.vpnhotspot.App.Companion.app
import be.mygod.vpnhotspot.BuildConfig
import be.mygod.vpnhotspot.MainActivity import be.mygod.vpnhotspot.MainActivity
import be.mygod.vpnhotspot.R import be.mygod.vpnhotspot.R
import be.mygod.vpnhotspot.databinding.ListitemInterfaceBinding import be.mygod.vpnhotspot.databinding.ListitemInterfaceBinding
@@ -215,12 +221,26 @@ sealed class TetherManager(protected val parent: TetheringFragment) : Manager(),
} }
@RequiresApi(24) @RequiresApi(24)
class Bluetooth(parent: TetheringFragment) : TetherManager(parent), DefaultLifecycleObserver { 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() } private val tethering = BluetoothTethering(parent.requireContext()) { data.notifyChange() }
init { init {
parent.viewLifecycleOwner.lifecycle.addObserver(this) 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 fun onDestroy(owner: LifecycleOwner) = tethering.close()
override val title get() = parent.getString(R.string.tethering_manage_bluetooth) override val title get() = parent.getString(R.string.tethering_manage_bluetooth)

View File

@@ -46,11 +46,12 @@ class TetheringFragment : Fragment(), ServiceConnection, Toolbar.OnMenuItemClick
internal val repeaterManager by lazy { RepeaterManager(this@TetheringFragment) } internal val repeaterManager by lazy { RepeaterManager(this@TetheringFragment) }
@get:RequiresApi(26) @get:RequiresApi(26)
internal val localOnlyHotspotManager by lazy @TargetApi(26) { LocalOnlyHotspotManager(this@TetheringFragment) } internal val localOnlyHotspotManager by lazy @TargetApi(26) { LocalOnlyHotspotManager(this@TetheringFragment) }
internal val bluetoothManager by lazy @TargetApi(24) { TetherManager.Bluetooth(this@TetheringFragment) }
@get:RequiresApi(24) @get:RequiresApi(24)
private val tetherManagers by lazy @TargetApi(24) { private val tetherManagers by lazy @TargetApi(24) {
listOf(TetherManager.Wifi(this@TetheringFragment), listOf(TetherManager.Wifi(this@TetheringFragment),
TetherManager.Usb(this@TetheringFragment), TetherManager.Usb(this@TetheringFragment),
TetherManager.Bluetooth(this@TetheringFragment)) bluetoothManager)
} }
@get:RequiresApi(30) @get:RequiresApi(30)
private val tetherManagers30 by lazy @TargetApi(30) { private val tetherManagers30 by lazy @TargetApi(30) {
@@ -130,6 +131,10 @@ class TetheringFragment : Fragment(), ServiceConnection, Toolbar.OnMenuItemClick
val startLocalOnlyHotspot = registerForActivityResult(ActivityResultContracts.RequestPermission()) { val startLocalOnlyHotspot = registerForActivityResult(ActivityResultContracts.RequestPermission()) {
adapter.localOnlyHotspotManager.start(requireContext()) adapter.localOnlyHotspotManager.start(requireContext())
} }
@RequiresApi(31)
val requestBluetooth = registerForActivityResult(ActivityResultContracts.RequestPermission()) { granted ->
if (granted) adapter.bluetoothManager.ensureInit(requireContext())
}
var ifaceLookup: Map<String, NetworkInterface> = emptyMap() var ifaceLookup: Map<String, NetworkInterface> = emptyMap()
var enabledTypes = emptySet<TetherType>() var enabledTypes = emptySet<TetherType>()

View File

@@ -186,7 +186,11 @@ sealed class TetheringTileService : IpNeighbourMonitoringTileService(), Tetherin
state = Tile.STATE_INACTIVE state = Tile.STATE_INACTIVE
icon = tileOff icon = tileOff
} }
null -> return null -> {
state = Tile.STATE_UNAVAILABLE
icon = tileOff
subtitle(tethering?.activeFailureCause?.readableMessage)
}
} }
label = getText(labelString) label = getText(labelString)
updateTile() updateTile()