BLE improve permission check for ScanCallback() in Kotlin - android

I'm working on a small app with a BLE device. Sometimes the scan callback works fine and I can find the device but other times the scan callback doesn't work and I was wondering if there's something wrong with my code or if there's a problem with my permission check.
Here's my code:
override fun onCreate(savedInstanceState: Bundle?) {
if (!isBLESupported(this)) {
Toast.makeText(this, "This device doesn't support bluetooth", Toast.LENGTH_SHORT).show()
finish()
} else {
if (!mBtAdapter!!.isEnabled) {
val enableBluetoothIntent = Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE)
startActivityForResult(enableBluetoothIntent, REQUEST_ENABLE_BLUETOOTH)
}
}
adapter = ArrayAdapter(this, android.R.layout.simple_list_item_1, macList)
list_view.adapter = adapter
private val lightUUID = UUID.fromString("0000ffb0-0000-1000-8000-00805f9b34fb")
scan_button.setOnClickListener {
scanForDeviceWithFilter(lightUUID)
}
}
private fun scanForDeviceWithFilter(serviceUUID: UUID) {
val uuid = ParcelUuid(id)
val filter = ScanFilter.Builder().setServiceUuid(uuid).build()
val filters = listOf(filter)
val settings = ScanSettings.Builder().setScanMode(ScanSettings.SCAN_MODE_LOW_LATENCY).build()
checkBTPermissions()
mBtAdapter!!.bluetoothLeScanner.startScan(filters, settings, scanDevicesCallback)
}
private val scanDevicesCallback = object : ScanCallback() {
override fun onScanResult(callbackType: Int, result: ScanResult?) {
result?.let {
if (!macList.contains(result.device.name.toString())) {
macList.add(result.device.name.toString())
adapter.notifyDataSetChanged()
}
Log.d(TAG, "device found:${result.device}")
}
}
override fun onScanFailed(errorCode: Int) {
Log.d(TAG, "Scan failed $errorCode")
}
}
private fun isBLESupported(context: Context): Boolean {
return BluetoothAdapter.getDefaultAdapter() != null && context.packageManager.hasSystemFeature(
PackageManager.FEATURE_BLUETOOTH_LE
)
}
private fun checkBTPermissions() {
val permissionCheck = checkSelfPermission("Manifest.permission.ACCESS_FINE_LOCATION")
if (permissionCheck != 0) {
requestPermissions(
arrayOf(Manifest.permission.ACCESS_FINE_LOCATION), 1001
)
}
}
init {
mBtAdapter = BluetoothAdapter.getDefaultAdapter()
}

Related

how do i get the set notification in BLE nordic

i am trying to set notification callback in BLE nordic, where i am using the Android BLE library (Nordic Github). But i not ablet to get the notification event when i am changing the value of the characteristics.
`
class BleManagerHP1T(context: Context) : BleManager(context) {
override fun getGattCallback(): BleManagerGattCallback = GattCallback()
override fun log(priority: Int, message: String) {
if (BuildConfig.DEBUG || priority == Log.ERROR) {
Log.println(priority, GattService.TAG, message)
}
}
private inner class GattCallback : BleManagerGattCallback() {
private var myCharacteristic: BluetoothGattCharacteristic? = null
private var rxCharacteristic: BluetoothGattCharacteristic? = null
#SuppressLint("MissingPermission")
override fun isRequiredServiceSupported(gatt: BluetoothGatt): Boolean {
val service = gatt.getService(GattService.MyServiceProfile.MY_SERVICE_UUID)
myCharacteristic =
service?.getCharacteristic(GattService.MyServiceProfile.MY_CHARACTERISTIC_UUID)
val myCharacteristicProperties = myCharacteristic?.properties ?: 0
Log.d(TAG, "isRequiredServiceSupported: notify ${(myCharacteristicProperties and BluetoothGattCharacteristic.PROPERTY_NOTIFY != 0)}")
rxCharacteristic = service?.getCharacteristic(GattService.MyServiceProfile.RX_CHARACTERISTIC_UUID)
val obj = JSONObject()
obj.put("OPCODE","PROVISION")
rxCharacteristic?.value = obj.toString().encodeToByteArray()
val rxRead = gatt.writeCharacteristic(rxCharacteristic)
Log.d(TAG, "isRequiredServiceSupported: Read $rxRead")
return (myCharacteristicProperties and BluetoothGattCharacteristic.PROPERTY_NOTIFY != 0)
}
override fun initialize() {
enableNotifications(myCharacteristic).enqueue()
requestMtu(260).enqueue();
setNotificationCallback(myCharacteristic).with { _, data ->
Log.d(TAG, "initialize: TX char Notification Called")
if (data.value != null) {
val value = String(data.value!!, Charsets.UTF_8)
Log.d(TAG, "initialize: TX char value $value")
}
}
requestMtu(260).enqueue();
enableNotifications(rxCharacteristic).enqueue()
setNotificationCallback(rxCharacteristic).with { _, data ->
Log.d(TAG, "initialize: RX char Notification Called")
if (data.value != null) {
val value = String(data.value!!, Charsets.UTF_8)
Log.d(TAG, "initialize: RX char value $value")
}
}
beginAtomicRequestQueue()
.add(enableNotifications(myCharacteristic)
.fail { _: BluetoothDevice?, status: Int ->
log(Log.ERROR, "Could not subscribe: $status")
disconnect().enqueue()
}
)
.done {
log(Log.INFO, "Target initialized")
}
.enqueue()
}
override fun onServicesInvalidated() {
myCharacteristic = null
}
}
override fun readCharacteristic(characteristic: BluetoothGattCharacteristic?): ReadRequest {
return Request.newReadRequest(characteristic)
}
}
`
gatt connection is establishing perfectly fine, using this code.
`
val bleManager = BleManagerHP1T(this#ControllerActivity)
synchronized (this) {
bleManager.connect(deviceMainList[position]).useAutoConnect(false).enqueue()
}
`
here is the gatt service file .
`
class GattService : Service() {
private val defaultScope = CoroutineScope(Dispatchers.Default)
private lateinit var bluetoothObserver: BroadcastReceiver
private var myCharacteristicChangedChannel: SendChannel<String>? = null
private val clientManagers = mutableMapOf<String, ClientManager>()
// val connect = BleManager()
#RequiresApi(Build.VERSION_CODES.O)
override fun onCreate() {
super.onCreate()
// Setup as a foreground service
val notificationChannel = NotificationChannel(
GattService::class.java.simpleName,
resources.getString(R.string.gatt_service_name),
NotificationManager.IMPORTANCE_DEFAULT
)
val notificationService =
getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
notificationService.createNotificationChannel(notificationChannel)
val notification = NotificationCompat.Builder(this, GattService::class.java.simpleName)
.setSmallIcon(R.mipmap.ic_launcher)
.setContentTitle(resources.getString(R.string.gatt_service_name))
.setContentText(resources.getString(R.string.gatt_service_running_notification))
.setAutoCancel(true)
startForeground(1, notification.build())
// Observe OS state changes in BLE
bluetoothObserver = object : BroadcastReceiver() {
#SuppressLint("MissingPermission")
override fun onReceive(context: Context?, intent: Intent?) {
when (intent?.action) {
BluetoothAdapter.ACTION_STATE_CHANGED -> {
val bluetoothState = intent.getIntExtra(
BluetoothAdapter.EXTRA_STATE,
-1
)
when (bluetoothState) {
BluetoothAdapter.STATE_ON -> enableBleServices()
BluetoothAdapter.STATE_OFF -> disableBleServices()
}
}
BluetoothDevice.ACTION_BOND_STATE_CHANGED -> {
val device =
intent.getParcelableExtra<BluetoothDevice>(BluetoothDevice.EXTRA_DEVICE)
Log.d(TAG, "Bond state changed for device ${device?.address}: ${device?.bondState}")
when (device?.bondState) {
BluetoothDevice.BOND_BONDED -> addDevice(device)
BluetoothDevice.BOND_NONE -> removeDevice(device)
}
}
}
}
}
registerReceiver(bluetoothObserver, IntentFilter(BluetoothAdapter.ACTION_STATE_CHANGED))
registerReceiver(bluetoothObserver, IntentFilter(BluetoothDevice.ACTION_BOND_STATE_CHANGED))
// Startup BLE if we have it
val bluetoothManager = getSystemService(Context.BLUETOOTH_SERVICE) as BluetoothManager
if (bluetoothManager.adapter?.isEnabled == true) enableBleServices()
}
override fun onDestroy() {
super.onDestroy()
unregisterReceiver(bluetoothObserver)
disableBleServices()
}
override fun onBind(intent: Intent?): IBinder? =
when (intent?.action) {
DATA_PLANE_ACTION -> {
DataPlane()
}
else -> null
}
override fun onUnbind(intent: Intent?): Boolean =
when (intent?.action) {
DATA_PLANE_ACTION -> {
myCharacteristicChangedChannel = null
true
}
else -> false
}
/**
* A binding to be used to interact with data of the service
*/
inner class DataPlane : Binder() {
fun setMyCharacteristicChangedChannel(sendChannel: SendChannel<String>) {
myCharacteristicChangedChannel = sendChannel
}
}
#SuppressLint("MissingPermission")
private fun enableBleServices() {
val bluetoothManager = getSystemService(Context.BLUETOOTH_SERVICE) as BluetoothManager
if (bluetoothManager.adapter?.isEnabled == true) {
Log.i(TAG, "Enabling BLE services")
bluetoothManager.adapter.bondedDevices.forEach { device -> addDevice(device) }
} else {
Log.w(TAG, "Cannot enable BLE services as either there is no Bluetooth adapter or it is disabled")
}
}
private fun disableBleServices() {
clientManagers.values.forEach { clientManager ->
clientManager.close()
}
clientManagers.clear()
}
private fun addDevice(device: BluetoothDevice) {
if (!clientManagers.containsKey(device.address)) {
val clientManager = ClientManager()
clientManager.connect(device).useAutoConnect(true).enqueue()
clientManagers[device.address] = clientManager
}
}
private fun removeDevice(device: BluetoothDevice) {
clientManagers.remove(device.address)?.close()
}
/*
* Manages the entire GATT service, declaring the services and characteristics on offer
*/
companion object {
/**
* A binding action to return a binding that can be used in relation to the service's data
*/
const val DATA_PLANE_ACTION = "data-plane"
const val TAG = "gatt-service"
}
private inner class ClientManager : BleManager(this#GattService) {
override fun getGattCallback(): BleManagerGattCallback = GattCallback()
override fun log(priority: Int, message: String) {
if (BuildConfig.DEBUG || priority == Log.ERROR) {
Log.println(priority, TAG, message)
Log.d(TAG, "log: $message")
}
}
private inner class GattCallback : BleManagerGattCallback() {
private var myCharacteristic: BluetoothGattCharacteristic? = null
override fun isRequiredServiceSupported(gatt: BluetoothGatt): Boolean {
val service = gatt.getService(MyServiceProfile.MY_SERVICE_UUID)
myCharacteristic =
service?.getCharacteristic(MyServiceProfile.MY_CHARACTERISTIC_UUID)
val myCharacteristicProperties = myCharacteristic?.properties ?: 0
return (myCharacteristicProperties and BluetoothGattCharacteristic.PROPERTY_READ != 0) &&
(myCharacteristicProperties and BluetoothGattCharacteristic.PROPERTY_NOTIFY != 0)
}
override fun initialize() {
setNotificationCallback(myCharacteristic).with { _, data ->
if (data.value != null) {
val value = String(data.value!!, Charsets.UTF_8)
defaultScope.launch {
myCharacteristicChangedChannel?.send(value)
}
}
}
beginAtomicRequestQueue()
.add(enableNotifications(myCharacteristic)
.fail { _: BluetoothDevice?, status: Int ->
log(Log.ERROR, "Could not subscribe: $status")
disconnect().enqueue()
}
)
.done {
log(Log.INFO, "Target initialized")
}
.enqueue()
}
override fun onServicesInvalidated() {
myCharacteristic = null
}
}
}
object MyServiceProfile {
val MY_SERVICE_UUID: UUID = UUID.fromString("8d67d51a-801b-43cb-aea2-bbec9d1211fd")
val MY_CHARACTERISTIC_UUID: UUID = UUID.fromString("8d67d51c-801b-43cb-aea2-bbec9d1211fd")
val RX_CHARACTERISTIC_UUID: UUID = UUID.fromString("8d67d51b-801b-43cb-aea2-bbec9d1211fd")
}
}
`

Kotlin Type mismatch: inferred type is Activity? but Context was expected ERROR after upgrading flutter to the 3.0.0

I am using beacon plugin. After I upgraded flutter to the version 3.0.0, it throwed this error:
Class 'BeaconsPlugin' is not abstract and does not implement abstract member public abstract fun onRequestPermissionsResult(p0: Int, p1: Array<(out) String!>, p2: IntArray): Boolean defined in io.flutter.plugin.common.PluginRegistry.RequestPermissionsResultListener
e: C:\Software\HR-Management-Localization\mapping_tool\packages\simple_beacons_flutter\android\src\main\kotlin\com\umair\beacons_plugin\BeaconsPlugin.kt: (66, 32): Type mismatch: inferred type is Activity? but Context was expected
e: C:\Software\HR-Management-Localization\mapping_tool\packages\simple_beacons_flutter\android\src\main\kotlin\com\umair\beacons_plugin\BeaconsPlugin.kt: (428, 5): 'onRequestPermissionsResult' overrides nothing
I've solved it by removing question marks from lines
permissions: Array<out String>?,
grantResults: IntArray?
to this
permissions: Array<out String>,
grantResults: IntArray
in onRequestPermissionsResult method.
And than it throwed a different error: BeaconsPlugin.kt: (66, 32): Type mismatch: inferred type is Activity? but Context was expected
I don't know kotlin therefore unable to solve this.
Here is my whole BeaconsPlugin.kt:
package com.umair.beacons_plugin
import android.Manifest
import android.app.Activity
import android.app.AlertDialog
import android.content.Context
import android.content.Intent
import android.content.pm.PackageManager
import android.os.Build
import androidx.annotation.NonNull
import androidx.core.app.ActivityCompat
import androidx.core.content.ContextCompat
import io.flutter.embedding.engine.plugins.FlutterPlugin
import io.flutter.embedding.engine.plugins.activity.ActivityAware
import io.flutter.embedding.engine.plugins.activity.ActivityPluginBinding
import io.flutter.plugin.common.*
import timber.log.Timber
/** BeaconsPlugin */
class BeaconsPlugin : FlutterPlugin, ActivityAware,
PluginRegistry.RequestPermissionsResultListener {
private var context: Context? = null
override fun onAttachedToEngine(#NonNull flutterPluginBinding: FlutterPlugin.FlutterPluginBinding) {
Timber.i("onAttachedToEngine")
messenger = flutterPluginBinding.binaryMessenger
setUpPluginMethods(
flutterPluginBinding.applicationContext,
flutterPluginBinding.binaryMessenger
)
context = flutterPluginBinding.applicationContext
beaconHelper = BeaconHelper(flutterPluginBinding.applicationContext)
context?.let {
BeaconPreferences.init(it)
stopBackgroundService(it)
}
}
companion object {
private val TAG = "BeaconsPlugin"
private var REQUEST_LOCATION_PERMISSIONS = 1890
private var PERMISSION_REQUEST_BACKGROUND_LOCATION = 1891
private var channel: MethodChannel? = null
private var event_channel: EventChannel? = null
private var currentActivity: Activity? = null
private var beaconHelper: BeaconHelper? = null
private var defaultPermissionDialogTitle = "This app needs background location access"
private var defaultPermissionDialogMessage =
"Please grant location access so this app can detect beacons in the background."
#JvmStatic
internal var messenger: BinaryMessenger? = null
#JvmStatic
fun registerWith(registrar: PluginRegistry.Registrar) {
BeaconPreferences.init(registrar.context())
if (beaconHelper == null) {
this.beaconHelper = BeaconHelper(registrar.context())
}
val instance = BeaconsPlugin()
registrar.addRequestPermissionsResultListener(instance)
//requestPermission()
setUpPluginMethods(registrar.activity(), registrar.messenger())
}
#JvmStatic
fun registerWith(messenger: BinaryMessenger, context: Context) {
BeaconPreferences.init(context)
if (beaconHelper == null) {
this.beaconHelper = BeaconHelper(context)
}
val instance = BeaconsPlugin()
//requestPermission()
setUpPluginMethods(context, messenger)
}
#JvmStatic
fun registerWith(messenger: BinaryMessenger, beaconHelper: BeaconHelper, context: Context) {
BeaconPreferences.init(context)
this.beaconHelper = beaconHelper
val instance = BeaconsPlugin()
//requestPermission()
setUpPluginMethods(context, messenger)
}
#JvmStatic
private fun setUpPluginMethods(context: Context, messenger: BinaryMessenger) {
Timber.plant(Timber.DebugTree())
channel = MethodChannel(messenger, "beacons_plugin")
notifyIfPermissionsGranted(context)
channel?.setMethodCallHandler { call, result ->
when {
call.method == "startMonitoring" -> {
stopService = false
callBack?.startScanning()
result.success("Started scanning Beacons.")
}
call.method == "stopMonitoring" -> {
if (runInBackground) {
stopService = true
context.let {
stopBackgroundService(it)
}
}
callBack?.stopMonitoringBeacons()
result.success("Stopped scanning Beacons.")
}
call.method == "addRegion" -> {
callBack?.addRegion(call, result)
}
call.method == "clearRegions" -> {
callBack?.clearRegions(call, result)
}
call.method == "runInBackground" -> {
call.argument<Boolean>("background")?.let {
runInBackground = it
}
result.success("App will run in background? $runInBackground")
}
call.method == "clearDisclosureDialogShowFlag" -> {
call.argument<Boolean>("clearFlag")?.let {
if (it) {
clearPermissionDialogShownFlag()
result.success("clearDisclosureDialogShowFlag: Flag cleared!")
} else {
setPermissionDialogShown()
result.success("clearDisclosureDialogShowFlag: Flag Set!")
}
}
}
call.method == "setDisclosureDialogMessage" -> {
call.argument<String>("title")?.let {
defaultPermissionDialogTitle = it
}
call.argument<String>("message")?.let {
defaultPermissionDialogMessage = it
}
requestPermission()
result.success("Disclosure message Set: $defaultPermissionDialogMessage")
}
call.method == "addBeaconLayoutForAndroid" -> {
call.argument<String>("layout")?.let {
callBack?.addBeaconLayout(it)
result.success("Beacon layout added: $it")
}
}
call.method == "setForegroundScanPeriodForAndroid" -> {
var foregroundScanPeriod = 1100L
var foregroundBetweenScanPeriod = 0L
call.argument<Int>("foregroundScanPeriod")?.let {
if (it > foregroundScanPeriod) {
foregroundScanPeriod = it.toLong()
}
}
call.argument<Int>("foregroundBetweenScanPeriod")?.let {
if (it > foregroundBetweenScanPeriod) {
foregroundBetweenScanPeriod = it.toLong()
}
}
callBack?.setForegroundScanPeriod(
foregroundScanPeriod = foregroundScanPeriod,
foregroundBetweenScanPeriod = foregroundBetweenScanPeriod
)
result.success("setForegroundScanPeriod updated.")
}
call.method == "setBackgroundScanPeriodForAndroid" -> {
var backgroundScanPeriod = 1100L
var backgroundBetweenScanPeriod = 0L
call.argument<Int>("backgroundScanPeriod")?.let {
if (it > backgroundScanPeriod) {
backgroundScanPeriod = it.toLong()
}
}
call.argument<Int>("backgroundBetweenScanPeriod")?.let {
if (it > backgroundBetweenScanPeriod) {
backgroundBetweenScanPeriod = it.toLong()
}
}
callBack?.setBackgroundScanPeriod(
backgroundScanPeriod = backgroundScanPeriod,
backgroundBetweenScanPeriod = backgroundBetweenScanPeriod
)
result.success("setBackgroundScanPeriod updated.")
}
else -> result.notImplemented()
}
}
event_channel = EventChannel(messenger, "beacons_plugin_stream")
event_channel?.setStreamHandler(object : EventChannel.StreamHandler {
override fun onListen(arguments: Any?, events: EventChannel.EventSink?) {
callBack?.setEventSink(events)
}
override fun onCancel(arguments: Any?) {
}
})
}
#JvmStatic
private fun notifyIfPermissionsGranted(context: Context) {
if (permissionsGranted(context)) {
doIfPermissionsGranted()
}
}
#JvmStatic
fun permissionsGranted(context: Context): Boolean {
return ContextCompat.checkSelfPermission(
context,
Manifest.permission.ACCESS_FINE_LOCATION
) == PackageManager.PERMISSION_GRANTED &&
ContextCompat.checkSelfPermission(
context,
Manifest.permission.ACCESS_COARSE_LOCATION
) == PackageManager.PERMISSION_GRANTED
}
#JvmStatic
private fun doIfPermissionsGranted() {
Timber.i("doIfPermissionsGranted")
if (beaconHelper == null) {
return
}
this.callBack = beaconHelper
sendBLEScannerReadyCallback()
}
#JvmStatic
private fun requestPermission() {
if (areBackgroundScanPermissionsGranted()) {
requestLocationPermissions()
} else {
requestBackgroundPermission()
}
}
private fun requestLocationPermissions() {
if (!arePermissionsGranted()) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
currentActivity?.let {
ActivityCompat.requestPermissions(
it,
arrayOf(
Manifest.permission.ACCESS_FINE_LOCATION,
Manifest.permission.ACCESS_COARSE_LOCATION,
Manifest.permission.ACCESS_BACKGROUND_LOCATION
),
REQUEST_LOCATION_PERMISSIONS
)
}
} else {
currentActivity?.let {
ActivityCompat.requestPermissions(
it,
arrayOf(
Manifest.permission.ACCESS_FINE_LOCATION,
Manifest.permission.ACCESS_COARSE_LOCATION
),
REQUEST_LOCATION_PERMISSIONS
)
}
}
} else {
doIfPermissionsGranted()
}
} else {
doIfPermissionsGranted()
}
}
#JvmStatic
private fun requestBackgroundPermission() {
if (!isPermissionDialogShown()) {
currentActivity?.let {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
//if (it.shouldShowRequestPermissionRationale(Manifest.permission.ACCESS_BACKGROUND_LOCATION)) {
val builder: AlertDialog.Builder =
AlertDialog.Builder(it)
builder.setTitle(defaultPermissionDialogTitle)
builder.setMessage(defaultPermissionDialogMessage)
builder.setPositiveButton("Ok", null)
builder.setOnDismissListener {
setPermissionDialogShown()
requestLocationPermissions()
channel?.invokeMethod("isPermissionDialogShown", "true")
}
builder.show()
//}
}
}
}
}
#JvmStatic
private fun arePermissionsGranted(): Boolean {
currentActivity?.let {
return ContextCompat.checkSelfPermission(
it,
Manifest.permission.ACCESS_FINE_LOCATION
) == PackageManager.PERMISSION_GRANTED &&
ContextCompat.checkSelfPermission(
it,
Manifest.permission.ACCESS_COARSE_LOCATION
) == PackageManager.PERMISSION_GRANTED
}
return false
}
#JvmStatic
private fun areBackgroundScanPermissionsGranted(): Boolean {
currentActivity?.let {
return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
ContextCompat.checkSelfPermission(
it,
Manifest.permission.ACCESS_BACKGROUND_LOCATION
) == PackageManager.PERMISSION_GRANTED
} else {
return true
}
}
return true
}
#JvmStatic
var runInBackground = false
#JvmStatic
var stopService = false
interface PluginImpl {
fun startScanning()
fun stopMonitoringBeacons()
fun addRegion(call: MethodCall, result: MethodChannel.Result)
fun clearRegions(call: MethodCall, result: MethodChannel.Result)
fun setEventSink(events: EventChannel.EventSink?)
fun addBeaconLayout(layout: String)
fun setForegroundScanPeriod(
foregroundScanPeriod: Long,
foregroundBetweenScanPeriod: Long
)
fun setBackgroundScanPeriod(
backgroundScanPeriod: Long,
backgroundBetweenScanPeriod: Long
)
}
private var callBack: PluginImpl? = null
fun sendBLEScannerReadyCallback() {
channel?.invokeMethod("scannerReady", "")
}
fun startBackgroundService(context: Context) {
if (runInBackground && !stopService) {
val serviceIntent1 = Intent(context, BeaconsDiscoveryService::class.java)
context.startService(serviceIntent1)
}
}
fun stopBackgroundService(context: Context) {
if (runInBackground && !stopService) {
val serviceIntent = Intent(context, BeaconsDiscoveryService::class.java)
context.stopService(serviceIntent)
}
}
}
override fun onDetachedFromEngine(#NonNull binding: FlutterPlugin.FlutterPluginBinding) {
currentActivity = null
channel?.setMethodCallHandler(null)
event_channel?.setStreamHandler(null)
if (!BeaconsPlugin.runInBackground)
beaconHelper?.stopMonitoringBeacons()
context?.let {
stopBackgroundService(it)
}
context = null
}
override fun onAttachedToActivity(activityPluginBinding: ActivityPluginBinding) {
currentActivity = activityPluginBinding.activity
BeaconPreferences.init(currentActivity)
activityPluginBinding.addRequestPermissionsResultListener(this)
//requestPermission()
if (arePermissionsGranted()) {
sendBLEScannerReadyCallback()
}
}
override fun onDetachedFromActivityForConfigChanges() {
Timber.i("onDetachedFromActivityForConfigChanges")
}
override fun onReattachedToActivityForConfigChanges(activityPluginBinding: ActivityPluginBinding) {
Timber.i("onReattachedToActivityForConfigChanges")
currentActivity = activityPluginBinding.activity
activityPluginBinding.addRequestPermissionsResultListener(this)
}
override fun onDetachedFromActivity() {
Timber.i("onDetachedFromActivity")
currentActivity = null
context?.let {
startBackgroundService(it)
}
}
override fun onRequestPermissionsResult(
requestCode: Int,
permissions: Array<out String>,
grantResults: IntArray
): Boolean {
if (requestCode == REQUEST_LOCATION_PERMISSIONS && grantResults?.isNotEmpty()!! && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
doIfPermissionsGranted()
return true
}
if (requestCode == PERMISSION_REQUEST_BACKGROUND_LOCATION && grantResults?.isNotEmpty()!! && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
//setPermissionDialogShown()
//requestPermission()
return true
}
return false
}
}
i have save problem in deferent class and solved by edit this one
#JvmStatic
fun registerWith(registrar: PluginRegistry.Registrar) {
val instance = BackgroundSttPlugin()
registrar.addRequestPermissionsResultListener(instance)
requestRecordPermission()
// this one "registrar.context()" make my problem solved
setUpPluginMethods(registrar.context(), registrar.messenger())
}

how to increase listening time of speech recognition (Speech to Text) android (SpeechRecognizer)

I am working on app which require speech to text. I have integrated SpeechRecognizer service. Please check below demo project code for same. Here I have tested that SpeechRecognizer stop listening automatically after 10 to 15 seconds. So I looked for following solution to increase the listening time but did not worked.
Solution 1: SpeechRecognizer - time limit
For the above link i found the problem is text getting cut like Said "Hello , what is the serial number of your currency, It is ABC5654548585" so in first result i got Hello, What is the serial number of your currency and in second i got it is ABC5654548585".So I am unable to parse response properly.
Solution 2: I set the following parameters but did not worked.
EXTRA_SPEECH_INPUT_COMPLETE_SILENCE_LENGTH_MILLIS
EXTRA_SPEECH_INPUT_MINIMUM_LENGTH_MILLIS
EXTRA_SPEECH_INPUT_POSSIBLY_COMPLETE_SILENCE_LENGTH_MILLIS.
Solution 3: I also integrated mozilla speech to text but it is not accurate as google.
Please help me I am stucked here.Not able to proceed any more.
class MainActivity : AppCompatActivity() {
lateinit var speechRecognize: SpeechRecognizer
var editText: EditText? = null
var micButton: ImageView? = null
var speechRecognizerIntent: Intent? = null
var audioManager: AudioManager? = null
var textRecorded = ""
var isActive = true
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
speechRecognize = SpeechRecognizer.createSpeechRecognizer(this)
audioManager = getSystemService(Context.AUDIO_SERVICE) as AudioManager?
muteRecognition(true)
editText = findViewById(R.id.text);
micButton = findViewById(R.id.button);
micButton?.setOnClickListener {
if (it.tag == null) {
isActive = true
launchSpeechIntent()
speechRecognize.startListening(speechRecognizerIntent)
it.tag = 1
} else if (it.tag == 1) {
isActive = false
parseText()
speechRecognize.stopListening()
speechRecognize.destroy()
it.tag = null
}
}
val permissions = ArrayList<String>()
if (ActivityCompat.checkSelfPermission(
this,
Manifest.permission.RECORD_AUDIO
)
!= PackageManager.PERMISSION_GRANTED
) {
permissions.add(Manifest.permission.RECORD_AUDIO)
}
if (ActivityCompat.checkSelfPermission(
this,
Manifest.permission.WRITE_EXTERNAL_STORAGE
)
!= PackageManager.PERMISSION_GRANTED
) {
permissions.add(Manifest.permission.WRITE_EXTERNAL_STORAGE)
}
if (permissions.isNotEmpty()) {
ActivityCompat.requestPermissions(
this,
permissions.toArray(arrayOf<String>()),
10
)
} else
launchSpeechIntent()
//else
// startRequest()
}
#Suppress("DEPRECATION")
private fun muteRecognition(mute: Boolean) {
audioManager?.let {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
val flag = if (mute) AudioManager.ADJUST_MUTE else AudioManager.ADJUST_UNMUTE
it.adjustStreamVolume(AudioManager.STREAM_NOTIFICATION, flag, 0)
it.adjustStreamVolume(AudioManager.STREAM_ALARM, flag, 0)
it.adjustStreamVolume(AudioManager.STREAM_MUSIC, flag, 0)
it.adjustStreamVolume(AudioManager.STREAM_RING, flag, 0)
it.adjustStreamVolume(AudioManager.STREAM_SYSTEM, flag, 0)
} else {
it.setStreamMute(AudioManager.STREAM_NOTIFICATION, mute)
it.setStreamMute(AudioManager.STREAM_ALARM, mute)
it.setStreamMute(AudioManager.STREAM_MUSIC, mute)
it.setStreamMute(AudioManager.STREAM_RING, mute)
it.setStreamMute(AudioManager.STREAM_SYSTEM, mute)
}
}
}
fun launchSpeechIntent() {
speechRecognizerIntent = Intent(RecognizerIntent.ACTION_RECOGNIZE_SPEECH);
speechRecognizerIntent?.putExtra(
RecognizerIntent.EXTRA_LANGUAGE_MODEL,
RecognizerIntent.LANGUAGE_MODEL_FREE_FORM
);
speechRecognizerIntent?.putExtra(RecognizerIntent.EXTRA_CALLING_PACKAGE, this.packageName)
speechRecognizerIntent?.putExtra(RecognizerIntent.EXTRA_LANGUAGE, Locale.getDefault());
speechRecognizerIntent?.putExtra(RecognizerIntent.EXTRA_MAX_RESULTS, 1)
speechRecognizerIntent?.putExtra(
RecognizerIntent.EXTRA_SPEECH_INPUT_POSSIBLY_COMPLETE_SILENCE_LENGTH_MILLIS,
30000
)
speechRecognize.setRecognitionListener(object : RecognitionListener {
override fun onReadyForSpeech(params: Bundle?) {
Log.e("ready for speeach", "true")
}
override fun onRmsChanged(rmsdB: Float) {
Log.e("RMS changed", rmsdB.toString())
}
override fun onBufferReceived(buffer: ByteArray?) {
Log.e("buffer", buffer.toString())
}
override fun onPartialResults(partialResults: Bundle?) {
Log.e("ready for speeach", "true" + partialResults.toString())
}
override fun onEvent(eventType: Int, params: Bundle?) {
Log.e("event", eventType.toString())
Log.e("params", params.toString())
}
override fun onBeginningOfSpeech() {
editText!!.setHint("Listening...........")
editText!!.setText("")
}
override fun onEndOfSpeech() {
}
override fun onError(error: Int) {
Log.e("Error", error.toString())
speechRecognize.startListening(speechRecognizerIntent)
//editText?.setText("Error:"+error.toString())
}
override fun onResults(results: Bundle?) {
val data: ArrayList<String>? =
results!!.getStringArrayList(SpeechRecognizer.RESULTS_RECOGNITION)
var flot = results!!.getFloatArray(SpeechRecognizer.CONFIDENCE_SCORES)
textRecorded += "#${data!!.first()}"
// restart()
}
})
}
fun restart() {
if (isActive) {
speechRecognize.destroy()
launchSpeechIntent()
speechRecognize.startListening(speechRecognizerIntent)
} else {
speechRecognize.stopListening()
speechRecognize.destroy()
}
}
override fun onResume() {
super.onResume()
}
fun parseText() {
try {
editText?.setText(textRecorded)
} catch (e: Exception) {
e.printStackTrace()
}
}
private fun checkPermission() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
ActivityCompat.requestPermissions(
this,
arrayOf(Manifest.permission.RECORD_AUDIO),
10
)
}
}
override fun onRequestPermissionsResult(
requestCode: Int,
permissions: Array<out String>,
grantResults: IntArray
) {
super.onRequestPermissionsResult(requestCode, permissions, grantResults)
when (requestCode) {
10 ->
if (grantResults[0] != PackageManager.PERMISSION_GRANTED) {
launchSpeechIntent()
}
}
}
override fun onDestroy() {
super.onDestroy()
speechRecognize.stopListening()
speechRecognize.destroy()
}
}

BLE - Bluetooth GATT service can't close connection

I have implemented BLE and it works. The flow is: DrawerActivity starts, it sets a fragmentA, which has BLE implementation, because I want active BLE only in fragmentA. So if you switch to fragmentB it should terminate the BLE connection and upair the device.
What happens is that the only time it completely disconnects is, when you close the app, or turn off the bluetooth. If you close the fragmentA and open it again it works from drawerActivity. If you do it again, so this is now the 3rd time, it won't pair to the BLE device. When I investigated further, it won't even find the correct BLE device.. Meaning if you run the fragment the 4th, 5th time it is the same result.
What I want to achieve is when onDestroy in Fragment is called it should disconnect from the BLE and destroy all references. And then if you go into the fragmentA again it should recreate everything again, no matter how many times you open the fragmentA.But now the device isn't found anymore, probably because it didn't disconnect properly and BLE device has old references or something.
This is how I disconnect.
This is onDestroy method:
override fun onDestroy() {
super.onDestroy()
activity?.unregisterReceiver(bluetoothReceiver)
bluetoothManager?.disconnectBluetoothService()
bluetoothManager = null
}
And in bluetoothManager
fun disconnectBluetoothService() {
bluetoothService?.disconnectGattServer()
}
And at the bluetoothService:
fun disconnectGattServer() {
mConnected = false
mBluetoothGatt?.disconnect()
mBluetoothGatt?.close()
mBluetoothGatt = null
}
Here are the all 3 files that are used for BLE.
FragmentA
private var bluetoothManager: MyBluetoothManager? = null
private val bluetoothReceiver = object : BroadcastReceiver() {
override fun onReceive(context: Context, intent: Intent) {
val action = intent.action
if (action == BluetoothAdapter.ACTION_STATE_CHANGED) {
when (intent.getIntExtra(BluetoothAdapter.EXTRA_STATE, BluetoothAdapter.ERROR)) {
BluetoothAdapter.STATE_OFF -> {}
BluetoothAdapter.STATE_ON -> {
initBluetoothIfPossible()
bluetoothManager?.scanForBluetoothDevicesIfPossible(true)
}
}
}
}
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
listenToBluetoothChanges()
}
override fun onDestroy() {
super.onDestroy()
activity?.unregisterReceiver(bluetoothReceiver)
bluetoothManager?.disconnectBluetoothService()
bluetoothManager = null
}
private fun listenToBluetoothChanges() {
val filter = IntentFilter(BluetoothAdapter.ACTION_STATE_CHANGED)
carSharingActivity?.registerReceiver(bluetoothReceiver, filter)
}
private fun initBluetoothIfPossible() {
bluetoothToken ?: return
if (bluetoothManager != null) {
bluetoothManager!!.pairDevice()
} else {
bluetoothManager = MyBluetoothManager(activity as Activity,
this,
bluetoothToken!!.token,
bluetoothToken!!.sessionKey,
bluetoothToken!!.uuid)
}
setImageForBluetoothStatus()
}
MyBluetoothManager
class ACCarBluetoothManager(var activity: Activity,
var listener: MyBluetoothListener,
private var token: String,
private var sessionKey: String,
private var accessDeviceUID: String) {
// Bluetooth adapter
private var bluetoothAdapter: BluetoothAdapter?
// Bluetooth service
private var bluetoothService: MyBluetoothService? = null
private var isBluetoothAvailable: Boolean = false
val isBluetoothEnabled: Boolean
get() = bluetoothAdapter?.isEnabled == true
var connectionStatus: Boolean = false
set(value) {
if (field == value) return
field = value
if (value) stopScanning()
else startScanning()
}
private var savedDevice: BluetoothDevice? = null
/**
* Service lifecyle management.
*/
private val serviceConnection = object : ServiceConnection {
override fun onServiceConnected(componentName: ComponentName, service: IBinder) {
bluetoothService = (service as MyBluetoothService.LocalBinder).service
bluetoothService?.isConnectedListener = { isConnected ->
listener.isConnected(isConnected)
connectionStatus = isConnected
}
isBluetoothAvailable = bluetoothService?.initialize() == true
}
override fun onServiceDisconnected(componentName: ComponentName) {
bluetoothService = null
connectionStatus = false
}
}
/**
* Broadcast receiver.
*/
private val gattUpdateReceiver = object : BroadcastReceiver() {
override fun onReceive(context: Context, intent: Intent) {
try {
when (intent.action) {
BluetoothConstants.ACTION_GATT_SERVICES_DISCOVERED -> bluetoothService?.initializeIndications()
BluetoothConstants.ACTION_INDICATIONS_INITIALIZED -> bluetoothService?.startAuthentication(token)
}
} catch (e: Exception) {
Log.e("GattUpdateReciever", e.message)
}
}
}
/**
* Bluetooth device scanning callback. The scanned device is added to the list of available
* devices.
*/
private val bluetoothScanCallback = object : ScanCallback() {
override fun onScanResult(callbackType: Int, result: ScanResult) {
super.onScanResult(callbackType, result)
val btDevice = result.device
if (btDevice.name.isNullOrEmpty()) return
if (deviceMatchesUID(btDevice)) {
savedDevice = btDevice
pairDevice()
}
}
}
init {
val gattServiceIntent = Intent(activity, MyBluetoothService::class.java)
activity.bindService(gattServiceIntent, this.serviceConnection, Context.BIND_AUTO_CREATE)
// Setup bluetooth adapter
val bluetoothManager = activity.getSystemService(Context.BLUETOOTH_SERVICE) as BluetoothManager
bluetoothAdapter = bluetoothManager.adapter
// If bluetooth is not enabled, request permission, otherwise start scanning process, Not IMPLEMENTED, because it is not needed.
scanForBluetoothDevicesIfPossible()
activity.registerReceiver(gattUpdateReceiver, BluetoothConstants.makeGattUpdateIntentFilter())
}
fun scanForBluetoothDevicesIfPossible(enable: Boolean = isBluetoothEnabled) {
val hasLocationPermission = ContextCompat.checkSelfPermission(activity, Manifest.permission.ACCESS_COARSE_LOCATION) == PackageManager.PERMISSION_GRANTED
if (enable) {
if (hasLocationPermission) {
startScanning()
}
//You can request for location permission if he doesn't have permission
} else {
stopScanning()
}
}
fun pairDevice() {
if (isBluetoothAvailable && savedDevice != null) {
bluetoothService?.connect(savedDevice!!)
}
}
fun startScanning() {
bluetoothAdapter?.bluetoothLeScanner?.startScan(bluetoothScanCallback)
}
fun stopScanning() {
bluetoothAdapter?.bluetoothLeScanner?.stopScan(bluetoothScanCallback)
}
fun deviceMatchesUID(device: BluetoothDevice): Boolean {
return device.name.equals(accessDeviceUID, ignoreCase = true)
}
}
MyBluetoothService
class ACCarBluetoothService : Service() {
var isConnectedListener: ((Boolean) -> Unit)? = null
var mConnected = false
set(value) {
field = value
isConnectedListener?.invoke(value)
}
private val mBinder = LocalBinder()
private var mBluetoothManager: BluetoothManager? = null
private var mBluetoothAdapter: BluetoothAdapter? = null
private var mBluetoothGatt: BluetoothGatt? = null
private var mDividedTokenList: MutableList<ByteArray>? = null
// Various callback methods defined by the BLE API.
private val mGattCallback = object : BluetoothGattCallback() {
override fun onConnectionStateChange(gatt: BluetoothGatt, status: Int, newState: Int) {
if (status == BluetoothGatt.GATT_FAILURE
|| status != BluetoothGatt.GATT_SUCCESS
|| newState == BluetoothProfile.STATE_DISCONNECTED) {
disconnectGattServer()
return
}
if (newState == BluetoothProfile.STATE_CONNECTED) {
gatt.discoverServices()
}
}
override fun onServicesDiscovered(gatt: BluetoothGatt, status: Int) {
if (status == BluetoothGatt.GATT_SUCCESS) onServiceDiscoveryReady()
}
override fun onDescriptorWrite(gatt: BluetoothGatt, descriptor: BluetoothGattDescriptor, status: Int) {
when {
descriptor.characteristic.uuid == BluetoothConstants.UUID_COMMAND_CHALLENGE -> setCharacteristicNotification(
BluetoothConstants.UUID_DEBUG,
true)
descriptor.characteristic.uuid == BluetoothConstants.UUID_DEBUG -> setCharacteristicNotification(
BluetoothConstants.UUID_STATUS_1,
true)
descriptor.characteristic.uuid == BluetoothConstants.UUID_STATUS_1 -> setCharacteristicNotification(
BluetoothConstants.UUID_STATUS_2,
true)
descriptor.characteristic.uuid == BluetoothConstants.UUID_STATUS_2-> setCharacteristicNotification(
BluetoothConstants.UUID_STATUS_3,
true)
else -> onIndicationsInitialized()
}
}
override fun onCharacteristicRead(gatt: BluetoothGatt, characteristic: BluetoothGattCharacteristic, status: Int) {
if (status == BluetoothGatt.GATT_SUCCESS) broadcastUpdate(characteristic)
}
override fun onCharacteristicChanged(gatt: BluetoothGatt, characteristic: BluetoothGattCharacteristic) {
if (characteristic.uuid == BluetoothConstants.UUID_COMMAND_CHALLENGE) {
commandChallenge = characteristic.value
} else {
broadcastUpdate(characteristic)
}
}
override fun onCharacteristicWrite(gatt: BluetoothGatt, characteristic: BluetoothGattCharacteristic, status: Int) {
if (BluetoothConstants.UUID_AUTHORIZE_PHONE == characteristic.uuid) writeNextPartToken()
}
}
override fun onBind(intent: Intent): IBinder? {
return mBinder
}
/**
* Initializes a reference to the local Bluetooth adapter.
*
* #return Return true if the initialization is successful.
*/
fun initialize(): Boolean {
if (mBluetoothManager == null) {
mBluetoothManager = getSystemService(Context.BLUETOOTH_SERVICE) as BluetoothManager
if (mBluetoothManager == null) return false
}
mBluetoothAdapter = mBluetoothManager!!.adapter
if (mBluetoothAdapter == null) return false
return true
}
fun initializeIndications() {
setCharacteristicNotification(BluetoothConstants.UUID_COMMAND_CHALLENGE, true)
}
fun startAuthentication(token: String) {
mDividedTokenList = Tools.divideArray(Tools.decodeBase64(token))
writeNextPartToken()
}
fun writeCommand(sessionKey: String, command: ByteArray) {
val safeCommand = Tools.generateSafeCommand(command, commandChallenge, Tools.decodeBase64(sessionKey))
val commandCharacteristic = mBluetoothGatt!!.getService(BluetoothConstants.UUID_CAR_CONTROL_SERVICE)
.getCharacteristic(BluetoothConstants.UUID_COMMAND_PHONE)
commandCharacteristic.value = safeCommand
mBluetoothGatt!!.writeCharacteristic(commandCharacteristic)
}
fun connect(device: BluetoothDevice) {
mBluetoothGatt = device.connectGatt(this, false, this.mGattCallback)
}
fun disconnectGattServer() {
mConnected = false
mBluetoothGatt?.disconnect()
mBluetoothGatt?.close()
mBluetoothGatt = null
}
private fun onIndicationsInitialized() {
val intent = Intent()
intent.action = BluetoothConstants.ACTION_INDICATIONS_INITIALIZED
sendBroadcast(intent)
}
private fun onServiceDiscoveryReady() {
val intent = Intent()
intent.action = BluetoothConstants.ACTION_GATT_SERVICES_DISCOVERED
sendBroadcast(intent)
}
private fun writeNextPartToken() {
if (mDividedTokenList!!.isEmpty()) {
broadcastUpdate(BluetoothConstants.ACTION_INIT_READY)
return
}
writeValue(BluetoothConstants.UUID_AUTHORIZE_PHONE, mDividedTokenList!!.removeAt(0))
}
private fun broadcastUpdate(action: String) {
val intent = Intent(action)
sendBroadcast(intent)
}
private fun writeValue(characteristicUUID: UUID, valueBytes: ByteArray) {
if (mBluetoothAdapter == null || mBluetoothGatt == null) return
val service = mBluetoothGatt!!.getService(BluetoothConstants.UUID_CAR_CONTROL_SERVICE)
val characteristic = service.getCharacteristic(characteristicUUID)
characteristic.value = valueBytes
mBluetoothGatt!!.writeCharacteristic(characteristic)
}
private fun setCharacteristicNotification(characteristicUUID: UUID, enabled: Boolean) {
if (mBluetoothAdapter == null || mBluetoothGatt == null) return
val characteristic = mBluetoothGatt!!
.getService(BluetoothConstants.UUID_CAR_INFORMATION_SERVICE)
.getCharacteristic(characteristicUUID)
mBluetoothGatt!!.setCharacteristicNotification(characteristic, enabled)
characteristic.getDescriptor(CONFIG_DESCRIPTOR)?.let {
it.value = if (enabled) BluetoothGattDescriptor.ENABLE_INDICATION_VALUE
else BluetoothGattDescriptor.DISABLE_NOTIFICATION_VALUE
mBluetoothGatt!!.writeDescriptor(it)
}
}
private fun broadcastUpdate(characteristic: BluetoothGattCharacteristic) {
val intent = Intent()
if (BluetoothConstants.UUID_STATUS_1 == characteristic.uuid) {
if (!hasDataInBluetooth(characteristic.value)) {
mConnected = true
statusListener?.invoke()
}
intent.action = BluetoothConstants.ACTION_STATUS_1_AVAILABLE
intent.putExtra(BluetoothConstants.EXTRA_DATA, characteristic.value)
}
if (BluetoothConstants.UUID_DEBUG == characteristic.uuid) {
intent.action = BluetoothConstants.ACTION_DEBUG_AVAILABLE
intent.putExtra(BluetoothConstants.EXTRA_DATA, characteristic.value)
}
if (BluetoothConstants.UUID_STATUS_2 == characteristic.uuid) {
intent.action = BluetoothConstants.ACTION_STATUS_1_AVAILABLE
intent.putExtra(BluetoothConstants.EXTRA_DATA, characteristic.value)
}
if (BluetoothConstants.UUID_STATUS_3 == characteristic.uuid) {
intent.action = BluetoothConstants.ACTION_STATUS_1_AVAILABLE
intent.putExtra(BluetoothConstants.EXTRA_DATA, characteristic.value)
}
sendBroadcast(intent)
}
private fun hasDataInBluetooth(byteArray: ByteArray): Boolean {
for (b in byteArray) {
if (b.toInt() != 0) {
return false
}
}
return true
}
inner class LocalBinder : Binder() {
val service: MyBluetoothService
get() = this#MyBluetoothService
}
}
I found the solution.
The problem vas in:
fun pairDevice() {
if (isBluetoothAvailable && savedDevice != null) {
bluetoothService?.connect(savedDevice!!)
}
}
Because it was trying to connect again and again it stopped broadcasting.
I solved it with:
fun pairDevice() {
if (isConnected) return
if (isBluetoothAvailable && savedDevice != null) {
bluetoothService?.connect(savedDevice!!)
isConnected = true
}
}

Android: How to detect Bluetooth connection status

I want to detect the connection status of a paired Bluetooth headset
to the phone.
In Android 3.0 (API level 11) "BluetoothHeadset" class has
"isAudioConnected()" method.
I don't know how to create (initialize) a "BluetoothHeadset" object.
It seems that I need to use
"getProfileProxy ()" but I need a sample code to find out how I need
to create and pass the parameters.
Thanks,
Hos
You need to implement BluetoothProfile.ServiceListener :
BluetoothProfile.ServiceListener b = new BlueToothListener();
boolean profileProxy = BluetoothAdapter.getDefaultAdapter()
.getProfileProxy(Handler.bot, b, BluetoothProfile.HEADSET);
public class BlueToothListener implements ServiceListener {
public static BluetoothHeadset headset;
public static BluetoothDevice bluetoothDevice;
#Override
public void onServiceDisconnected(int profile) {// dont care
headset = null;
}
#Override
public void onServiceConnected(int profile,
BluetoothProfile proxy) {// dont care
try {
Debugger.test("BluetoothProfile onServiceConnected "+proxy);
if (proxy instanceof BluetoothHeadset)
headset = ((BluetoothHeadset) proxy);
else// getProfileProxy(Handler.bot, b, BluetoothProfile.HEADSET);
return;// ^^ => NEVER
List<BluetoothDevice> connectedDevices = proxy
.getConnectedDevices();
for (BluetoothDevice device : connectedDevices) {
Debugger.log("BluetoothDevice found :" + device);
bluetoothDevice = device;
int connectionState = headset.getConnectionState(bluetoothDevice);
Debugger.log("BluetoothHeadset connectionState "+connectionState);//2 == OK
boolean startVoiceRecognition = headset
.startVoiceRecognition(device);
if (startVoiceRecognition) {
Debugger
.log("BluetoothHeadset init Listener OK");
return;
}
else
Notify.popup("Bluetooth headset can't start speech recognition");
}
} catch (Exception e) {
// }
}
}
}
`
Monitoring of the BT status can be done indeed by polling. Here's how I did it (full sample here) . Note that it's just a sample and you should manage the polling better:
manifest
<uses-permission android:name="android.permission.BLUETOOTH" />
<uses-permission android:name="android.permission.BLUETOOTH_CONNECT" />
gradle
implementation 'androidx.core:core-ktx:1.9.0'
implementation 'com.google.android.material:material:1.7.0'
implementation 'androidx.constraintlayout:constraintlayout:2.1.4'
implementation "androidx.work:work-runtime-ktx:2.7.1"
MainActivityViewModel.kt
#UiThread
class MainActivityViewModel(application: Application) : BaseViewModel(application) {
private val bluetoothAdapter: BluetoothAdapter =
context.getSystemService<BluetoothManager>()!!.adapter
private var bluetoothHeadsetProfile: BluetoothProfile? = null
val connectedDevicesLiveData =
DistinctLiveDataWrapper(MutableLiveData<ConnectedDevicesState>(ConnectedDevicesState.Idle))
val bluetoothTurnedOnLiveData = DistinctLiveDataWrapper(MutableLiveData<Boolean?>(null))
val isConnectedToBtHeadsetLiveData = DistinctLiveDataWrapper(MutableLiveData<Boolean?>(null))
private val pollingBtStateRunnable: Runnable
init {
updateBtStates()
pollingBtStateRunnable = object : Runnable {
override fun run() {
updateBtStates()
handler.postDelayed(this, POLLING_TIME_IN_MS)
}
}
// Establish connection to the proxy.
val serviceListener = object : BluetoothProfile.ServiceListener {
override fun onServiceConnected(profile: Int, bluetoothProfile: BluetoothProfile) {
this#MainActivityViewModel.bluetoothHeadsetProfile = bluetoothProfile
handler.removeCallbacks(pollingBtStateRunnable)
pollingBtStateRunnable.run()
}
override fun onServiceDisconnected(profile: Int) {
handler.removeCallbacks(pollingBtStateRunnable)
updateBtStates()
}
}
bluetoothAdapter.getProfileProxy(context, serviceListener, BluetoothProfile.HEADSET)
onClearedListeners.add {
this.bluetoothHeadsetProfile?.let { bluetoothProfile ->
bluetoothAdapter.closeProfileProxy(BluetoothProfile.HEADSET, bluetoothProfile)
}
handler.removeCallbacks(pollingBtStateRunnable)
}
}
fun initWithLifecycle(lifecycle: Lifecycle) {
lifecycle.addObserver(object : DefaultLifecycleObserver {
override fun onResume(owner: LifecycleOwner) {
super.onResume(owner)
pollingBtStateRunnable.run()
}
override fun onPause(owner: LifecycleOwner) {
super.onPause(owner)
handler.removeCallbacks(pollingBtStateRunnable)
}
})
}
#UiThread
private fun updateBtStates() {
// Log.d("AppLog", "updateBtStates")
val isBlueToothTurnedOn = bluetoothAdapter.state == BluetoothAdapter.STATE_ON
bluetoothTurnedOnLiveData.value = isBlueToothTurnedOn
if (!isBlueToothTurnedOn) {
connectedDevicesLiveData.value = ConnectedDevicesState.BluetoothIsTurnedOff
isConnectedToBtHeadsetLiveData.value = false
return
}
val isConnectedToBtHeadset = try {
bluetoothAdapter.getProfileConnectionState(BluetoothProfile.HEADSET) == BluetoothAdapter.STATE_CONNECTED
} catch (e: SecurityException) {
null
}
isConnectedToBtHeadsetLiveData.value = isConnectedToBtHeadset
val bluetoothProfile = bluetoothHeadsetProfile
if (bluetoothProfile != null) {
if (ActivityCompat.checkSelfPermission(context, Manifest.permission.BLUETOOTH_CONNECT) == PackageManager.PERMISSION_GRANTED) {
val connectedDevicesSet = bluetoothProfile.connectedDevices.toHashSet()
val previousConnectedDevices =
(connectedDevicesLiveData.value as? ConnectedDevicesState.GotResult)?.connectedDevices
if (previousConnectedDevices == null || previousConnectedDevices != connectedDevicesSet)
connectedDevicesLiveData.value =
ConnectedDevicesState.GotResult(connectedDevicesSet)
} else {
connectedDevicesLiveData.value =
ConnectedDevicesState.NeedBlueToothConnectPermission
}
} else {
connectedDevicesLiveData.value = ConnectedDevicesState.Idle
}
}
companion object {
private const val POLLING_TIME_IN_MS = 500L
}
}
MainActivity.kt
class MainActivity : AppCompatActivity() {
private lateinit var viewModel: MainActivityViewModel
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
viewModel = ViewModelProvider(this)[MainActivityViewModel::class.java]
viewModel.initWithLifecycle(lifecycle)
viewModel.bluetoothTurnedOnLiveData.observe(this) {
Log.d("AppLog", "MainActivity bluetoothTurnedOnLiveData BT turned on? $it")
}
viewModel.isConnectedToBtHeadsetLiveData.observe(this) {
Log.d("AppLog", "MainActivity isConnectedToBtHeadsetLiveData BT headset connected? $it")
}
viewModel.connectedDevicesLiveData.observe(this) {
Log.d("AppLog", "MainActivity connectedDevicesLiveData devices: $it")
}
findViewById<View>(R.id.grantBtPermission).setOnClickListener {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
requestPermissions(arrayOf(Manifest.permission.BLUETOOTH_CONNECT), 1)
}
}
}
}
ConnectedDevicesState.kt
sealed class ConnectedDevicesState {
object Idle : ConnectedDevicesState() {
override fun toString(): String {
if (BuildConfig.DEBUG)
return "Idle"
return super.toString()
}
}
class GotResult(#Suppress("MemberVisibilityCanBePrivate") val connectedDevices: Set<BluetoothDevice>) : ConnectedDevicesState() {
override fun toString(): String {
if (BuildConfig.DEBUG) {
return "GotResult: connectedDevices:${
connectedDevices.map {
try {
it.name
} catch (e: SecurityException) {
it.address
}
}
}"
}
return super.toString()
}
}
object BluetoothIsTurnedOff : ConnectedDevicesState() {
override fun toString(): String {
if (BuildConfig.DEBUG)
return "BluetoothIsTurnedOff"
return super.toString()
}
}
object NeedBlueToothConnectPermission : ConnectedDevicesState() {
override fun toString(): String {
if (BuildConfig.DEBUG)
return "NeedBlueToothConnectPermission"
return super.toString()
}
}
}
DistinctLiveDataWrapper.kt
class DistinctLiveDataWrapper<T>(#Suppress("MemberVisibilityCanBePrivate") val mutableLiveData: MutableLiveData<T>) {
#Suppress("MemberVisibilityCanBePrivate")
val distinctLiveData = Transformations.distinctUntilChanged(mutableLiveData)
var value: T?
#UiThread
set(value) {
mutableLiveData.value = value
}
get() {
return mutableLiveData.value
}
#AnyThread
fun postValue(value: T) {
mutableLiveData.postValue(value)
}
fun observe(lifecycleOwner: LifecycleOwner, observer: Observer<in T>) {
distinctLiveData.observe(lifecycleOwner, observer)
}
}
BaseViewModel.kt
/**usage: class MyViewModel(application: Application) : BaseViewModel(application)
* getting instance: private lateinit var viewModel: MyViewModel
* viewModel=ViewModelProvider(this).get(MyViewModel::class.java)*/
abstract class BaseViewModel(application: Application) : AndroidViewModel(application) {
#Suppress("MemberVisibilityCanBePrivate")
var isCleared = false
#Suppress("MemberVisibilityCanBePrivate")
val onClearedListeners = ArrayList<Runnable>()
#Suppress("unused")
#SuppressLint("StaticFieldLeak")
val context: Context = application.applicationContext
#Suppress("unused")
val handler = Handler(Looper.getMainLooper())
override fun onCleared() {
super.onCleared()
isCleared = true
onClearedListeners.forEach { it.run() }
}
}

Categories

Resources