Support BLUETOOTH_CONNECT permission on Android 12 beta
This commit is contained in:
@@ -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"
|
||||||
|
|||||||
@@ -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)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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)
|
||||||
|
|||||||
@@ -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>()
|
||||||
|
|||||||
@@ -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()
|
||||||
|
|||||||
Reference in New Issue
Block a user