My task is to find a way (if there is one) to detect when an outgoing phone call is answered by the receiver.
After some research, I have tried the following:
A. BroadcastReceiver for PHONE_STATE action
<receiver android:name=".Interceptor">
<intent-filter>
<action android:name="android.intent.action.PHONE_STATE"/>
</intent-filter>
</receiver>
but outgoing calls never notifies back when phone call is answered.
class Interceptor: BroadcastReceiver() {
override fun onReceive(context: Context, intent: Intent) {
try {
when (intent.getStringExtra(TelephonyManager.EXTRA_STATE)) {
TelephonyManager.EXTRA_STATE_RINGING -> {
showToast(context, "ringing")
}
TelephonyManager.EXTRA_STATE_IDLE -> {
showToast(context, "idle")
}
TelephonyManager.EXTRA_STATE_OFFHOOK -> {
showToast(context, "off hook")
}
else -> {
showToast(context, intent.getStringExtra(TelephonyManager.EXTRA_STATE)!!)
}
}
} catch (e: Exception) {
e.printStackTrace()
}
}
private fun showToast(context: Context, s: String) {
Toast.makeText(context, s, Toast.LENGTH_SHORT).show()
}
}
i'm aware of READ_PRECISE_PHONE_STATE, but such permission is only for applications such as dialers, carrier applications, or ims applications.
B. TelecomManager + ConnectionService
#RequiresApi(Build.VERSION_CODES.M)
fun call(view: View) {
val telecomManager: TelecomManager = getSystemService(TELECOM_SERVICE) as TelecomManager
when {
ContextCompat.checkSelfPermission(this, Manifest.permission.CALL_PHONE) != PackageManager.PERMISSION_GRANTED -> {
ActivityCompat.requestPermissions(this, arrayOf(Manifest.permission.CALL_PHONE), 2333)
}
ContextCompat.checkSelfPermission(this, Manifest.permission.READ_PHONE_STATE) != PackageManager.PERMISSION_GRANTED -> {
ActivityCompat.requestPermissions(this, arrayOf(Manifest.permission.READ_PHONE_STATE), 2334)
}
else -> {
try {
val phoneAccountHandle = PhoneAccountHandle(ComponentName(applicationContext, MyConnectionService::class.java), "dld")
val build = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
PhoneAccount
.builder(phoneAccountHandle, "1234") // phone account named 1234
.setCapabilities(PhoneAccount.CAPABILITY_CONNECTION_MANAGER or PhoneAccount.CAPABILITY_CALL_PROVIDER)
.build()
} else {
TODO("VERSION.SDK_INT < O")
}
telecomManager.registerPhoneAccount(build)
val parse = Uri.fromParts(PhoneAccount.SCHEME_TEL, PHONENUMBERTOCALL, null) // PHONENUMBERTOCALL == "1112223334", a valid telephone number
val extras = Bundle()
extras.putParcelable(EXTRA_PHONE_ACCOUNT_HANDLE, phoneAccountHandle)
telecomManager.placeCall(parse, extras)
} catch (e: SecurityException) {
e.printStackTrace()
}
}
}
}
#RequiresApi(Build.VERSION_CODES.M)
class MyConnectionService : ConnectionService() {
private val TAG = this#MyConnectionService::javaClass.name
override fun onCreate() {
super.onCreate()
Log.d(TAG, "onCreate: ")
}
override fun onStartCommand(intent: Intent, flags: Int, startId: Int): Int {
Log.d(TAG, "onStartCommand: ")
return super.onStartCommand(intent, flags, startId)
}
override fun onCreateIncomingConnection(connectionManagerPhoneAccount: PhoneAccountHandle, request: ConnectionRequest): Connection {
Log.d(TAG, "onCreateIncomingConnection: ")
return super.onCreateIncomingConnection(connectionManagerPhoneAccount, request)
}
override fun onCreateIncomingConnectionFailed(connectionManagerPhoneAccount: PhoneAccountHandle, request: ConnectionRequest) {
Log.d(TAG, "onCreateIncomingConnectionFailed: ")
super.onCreateIncomingConnectionFailed(connectionManagerPhoneAccount, request)
}
override fun onCreateOutgoingConnectionFailed(connectionManagerPhoneAccount: PhoneAccountHandle, request: ConnectionRequest) {
Log.d(TAG, "onCreateOutgoingConnectionFailed: ")
super.onCreateOutgoingConnectionFailed(connectionManagerPhoneAccount, request)
}
#RequiresApi(Build.VERSION_CODES.N_MR1)
override fun onCreateOutgoingConnection(connectionManagerPhoneAccount: PhoneAccountHandle, request: ConnectionRequest): Connection {
Log.d(TAG, "onCreateOutgoingConnection: ")
val connection = MyConnection(false)
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
connection.connectionProperties = Connection.PROPERTY_SELF_MANAGED
}
connection.setAddress(
request.address,
TelecomManager.PRESENTATION_ALLOWED)
connection.extras = request.extras
connection.setInitialized()
return connection
}
override fun onCreateOutgoingHandoverConnection(fromPhoneAccountHandle: PhoneAccountHandle, request: ConnectionRequest): Connection {
Log.d(TAG, "onCreateOutgoingHandoverConnection: ")
return super.onCreateOutgoingHandoverConnection(fromPhoneAccountHandle, request)
}
override fun onCreateIncomingHandoverConnection(fromPhoneAccountHandle: PhoneAccountHandle, request: ConnectionRequest): Connection {
Log.d(TAG, "onCreateIncomingHandoverConnection: ")
return super.onCreateIncomingHandoverConnection(fromPhoneAccountHandle, request)
}
override fun onHandoverFailed(request: ConnectionRequest, error: Int) {
super.onHandoverFailed(request, error)
Log.d(TAG, "onHandoverFailed: ")
}
override fun onConference(connection1: Connection, connection2: Connection) {
super.onConference(connection1, connection2)
Log.d(TAG, "onConference: ")
}
override fun onRemoteConferenceAdded(conference: RemoteConference) {
super.onRemoteConferenceAdded(conference)
Log.d(TAG, "onRemoteConferenceAdded: ")
}
override fun onRemoteExistingConnectionAdded(connection: RemoteConnection) {
super.onRemoteExistingConnectionAdded(connection)
Log.d(TAG, "onRemoteExistingConnectionAdded: ")
}
override fun onConnectionServiceFocusLost() {
super.onConnectionServiceFocusLost()
Log.d(TAG, "onConnectionServiceFocusLost: ")
}
override fun onConnectionServiceFocusGained() {
super.onConnectionServiceFocusGained()
Log.d(TAG, "onConnectionServiceFocusGained: ")
}
}
#RequiresApi(Build.VERSION_CODES.M)
class MyConnection(isIncoming: Boolean): Connection() {
private val TAG = this#MyConnection::javaClass.name
init {
// Assume all calls are video capable.
// Assume all calls are video capable.
}
override fun onStateChanged(state: Int) {
super.onStateChanged(state)
Log.d(TAG, "onStateChanged: $state")
}
override fun onAnswer() {
super.onAnswer()
this.setActive()
Log.d(TAG, "onAnswer: ")
}
override fun onDisconnect() {
super.onDisconnect()
this.destroy()
Log.d(TAG, "onDisconnect: ")
}
}
but none of those callbacks triggers
C. (alternate solution) Reading call logs
private fun showCallLogs() {
if (ContextCompat.checkSelfPermission(this, Manifest.permission.READ_CALL_LOG) != PackageManager.PERMISSION_GRANTED) {
ActivityCompat.requestPermissions(this, arrayOf(Manifest.permission.READ_CALL_LOG), PERMISSION_REQUEST_READ_LOGS)
} else {
val sb = StringBuffer()
val cursor = managedQuery(CallLog.Calls.CONTENT_URI, null, null, null, null)
// columns
val number = cursor.getColumnIndex(CallLog.Calls.NUMBER)
val duration = cursor.getColumnIndex(CallLog.Calls.DURATION)
val date = cursor.getColumnIndex(CallLog.Calls.DATE)
val type = cursor.getColumnIndex(CallLog.Calls.TYPE)
sb.append("Call Log : ")
while (cursor.moveToNext()) {
val phoneNumber = cursor.getString(number)
if (phoneNumber == PHONENUMBERTOCALL) {
// -- duration
val callDate = cursor.getString(date)
val callDayTime = Date(callDate.toLong())
// -- duration
if (callDayTime.month in 2..4) {
// -- duration
val callDuration = cursor.getString(duration)
// -- duration
// -- call type
val callType = cursor.getString(type)
val dirCode = callType.toInt()
var dir = ""
when (dirCode) {
CallLog.Calls.OUTGOING_TYPE -> dir = "OUTGOING"
CallLog.Calls.INCOMING_TYPE -> dir = "INCOMING"
CallLog.Calls.MISSED_TYPE -> dir = "MISSED"
}
// -- call type
sb.append("\n call report: \n date: $callDayTime \n call type: $dir \n duration: $callDuration")
sb.append("\n --------------------------------------------------")
}
}
}
cursor.close()
Log.d("INFO", sb.toString())
}
}
this solution works, but there will be several problems when publishing the app in the PlayStore, so this will never see the light (unfortunatelly).
Any help is appreciated, thank you in advance.
Related
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")
}
}
`
I had Enabled the Google Android Device Verification API.
I had added the SHA-256 onto Firebase setting and updated the GSON file.
and After adding :
Firebase.auth.firebaseAuthSettings.setAppVerificationDisabledForTesting(true)
I am getting error that SafetyNet or Captcha are not succeded (kind of error).
Can anyone tell me how can i disable the captcha check ?
Here is my code
class OTPNewActivity : AppCompatActivity(), OnKeyboardVisibilityListener, View.OnClickListener {
var TAG = "OTPNewActivity"
lateinit var binding: ActivityOtpnewBinding
val action = "android.provider.Telephony.SMS_RECEIVED"
var userEnteredCode = ""
var systemGeneratedCode = ""
var phoneNumer = ""
var phoneDigits = ""
private lateinit var auth: FirebaseAuth
private lateinit var resendToken: PhoneAuthProvider.ForceResendingToken
private var callbacks: PhoneAuthProvider.OnVerificationStateChangedCallbacks =
object : PhoneAuthProvider.OnVerificationStateChangedCallbacks() {
override fun onVerificationCompleted(credential: PhoneAuthCredential) {
Log.d(TAG, "onVerificationCompleted: $credential")
val code = credential.smsCode
if (code != null) {
binding.otpView.setText(code)
verifyPhoneNumberWithCode(systemGeneratedCode, code!!)
}
}
override fun onVerificationFailed(e: FirebaseException) {
Log.d(TAG, "onVerificationFailed $e")
if (e is FirebaseAuthInvalidCredentialsException) {
Constants.showToast(
this#OTPNewActivity,
Constants.TOAST_TYPE_FAIL,
"Invalid request"
)
} else if (e is FirebaseTooManyRequestsException) {
Constants.showToast(
this#OTPNewActivity,
Constants.TOAST_TYPE_FAIL,
"The SMS quota for the project has been exceeded $e"
)
} else {
Constants.showToast(
this#OTPNewActivity,
Constants.TOAST_TYPE_FAIL, "Something wents wrong"
)
}
}
override fun onCodeSent(
verificationId: String,
token: PhoneAuthProvider.ForceResendingToken
) {
// The SMS verification code has been sent to the provided phone number, we
// now need to ask the user to enter the code and then construct a credential
// by combining the code with a verification ID.
Log.d(TAG, "onCodeSent: $verificationId")
systemGeneratedCode = verificationId
resendToken = token
countdownTimer()
}
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = DataBindingUtil.setContentView(this, R.layout.activity_otpnew)
initListeners()
}
private fun initListeners() {
LocalSharedPreference.getInstance(this).isPhoneNumberVerified = false
// Firebase.auth.firebaseAuthSettings.setAppVerificationDisabledForTesting(true)
auth = Firebase.auth
setKeyboardVisibilityListener(this)
binding.btnNext.setOnClickListener(this)
binding.tvCount.setOnClickListener(this)
binding.icBack.setOnClickListener(this)
val intent = intent
intent?.let {
phoneNumer = intent.getStringExtra(Constants.PHONE_NUMBER).toString()
phoneDigits = intent.getStringExtra(Constants.SAVE_PHONE_DIGITS).toString()
binding.textView.text =
"${this.resources.getString(R.string.digit_code)} $phoneNumer"
val options = PhoneAuthOptions.newBuilder(auth)
.setPhoneNumber(phoneNumer)
.setTimeout(15L, TimeUnit.SECONDS)
.setActivity(this)
.setCallbacks(callbacks)
.build()
PhoneAuthProvider.verifyPhoneNumber(options)
}
binding.otpView.setOtpCompletionListener(OnOtpCompletionListener { otp -> // do Stuff
userEnteredCode = otp
binding.icNext.visibility = View.VISIBLE
binding.pbNext.visibility = View.GONE
verifyPhoneNumberWithCode(systemGeneratedCode, userEnteredCode)
})
}
private fun verifyPhoneNumberWithCode(verificationId: String?, code: String) {
try {
val credential = PhoneAuthProvider.getCredential(verificationId!!, code)
signInWithPhoneAuthCredential(credential);
} catch (e: Exception) {
binding.otpView.setText("")
Constants.showToast(
this#OTPNewActivity,
Constants.TOAST_TYPE_FAIL,
this#OTPNewActivity.resources.getString(R.string.wrong_Code)
)
e.printStackTrace()
}
}
private fun signInWithPhoneAuthCredential(credential: PhoneAuthCredential) {
auth.signInWithCredential(credential)
.addOnCompleteListener(this) { task ->
if (task.isSuccessful) {
// Sign in success, update UI with the signed-in user's information
Log.d(TAG, "signInWithCredential:success")
LocalSharedPreference.getInstance(this).isPhoneNumberVerified = true
if (phoneNumer.contains("+52")) {
LocalSharedPreference.getInstance(this).setSaveCountry("MX")
} else if (phoneNumer.contains("+92")) {
LocalSharedPreference.getInstance(this).setSaveCountry("PK")
} else if (phoneNumer.contains("+1")) {
LocalSharedPreference.getInstance(this).setSaveCountry("US")
}
LocalSharedPreference.getInstance(this).savePhoneNumber(phoneNumer)
LocalSharedPreference.getInstance(this).setPhoneDigits(phoneDigits)
val user = task.result?.user
val intent = Intent(this#OTPNewActivity, ProfileActivity::class.java)
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TASK or Intent.FLAG_ACTIVITY_CLEAR_TOP)
startActivity(intent)
finish()
} else {
// Sign in failed, display a message and update the UI
Log.w(TAG, "signInWithCredential:failure", task.exception)
if (task.exception is FirebaseAuthInvalidCredentialsException) {
Constants.showToast(
this#OTPNewActivity,
Constants.TOAST_TYPE_FAIL,
"${task.exception}"
)
}
// Update UI
}
}
}
private fun setKeyboardVisibilityListener(onKeyboardVisibilityListener: OnKeyboardVisibilityListener) {
val parentView: View = (findViewById<View>(android.R.id.content) as ViewGroup).getChildAt(0)
parentView.getViewTreeObserver()
.addOnGlobalLayoutListener(object : ViewTreeObserver.OnGlobalLayoutListener {
private var alreadyOpen = false
private val defaultKeyboardHeightDP = 100
private val EstimatedKeyboardDP =
defaultKeyboardHeightDP + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) 48 else 0
private val rect: Rect = Rect()
override fun onGlobalLayout() {
val estimatedKeyboardHeight = TypedValue.applyDimension(
TypedValue.COMPLEX_UNIT_DIP,
EstimatedKeyboardDP.toFloat(),
parentView.getResources().getDisplayMetrics()
)
.toInt()
parentView.getWindowVisibleDisplayFrame(rect)
val heightDiff: Int =
parentView.getRootView().getHeight() - (rect.bottom - rect.top)
val isShown = heightDiff >= estimatedKeyboardHeight
if (isShown == alreadyOpen) {
Log.d("Keyboard state", "Ignoring global layout change...")
return
}
alreadyOpen = isShown
onKeyboardVisibilityListener.onVisibilityChanged(isShown)
}
})
}
override fun onConfigurationChanged(newConfig: Configuration) {
super.onConfigurationChanged(newConfig)
// Checks whether a hardware keyboard is available
if (newConfig.hardKeyboardHidden === Configuration.HARDKEYBOARDHIDDEN_NO) {
Toast.makeText(this, "keyboard visible", Toast.LENGTH_SHORT).show()
} else if (newConfig.hardKeyboardHidden === Configuration.HARDKEYBOARDHIDDEN_YES) {
Toast.makeText(this, "keyboard hidden", Toast.LENGTH_SHORT).show()
}
}
override fun onVisibilityChanged(visible: Boolean) {
if (!visible) {
val imm: InputMethodManager =
getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager
imm.showSoftInput(binding.otpView, InputMethodManager.SHOW_IMPLICIT)
}
}
override fun onResume() {
super.onResume()
binding.otpView.requestFocus()
val imm: InputMethodManager =
getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager
imm.showSoftInput(binding.otpView, InputMethodManager.SHOW_IMPLICIT)
imm.toggleSoftInput(InputMethodManager.SHOW_FORCED, 0)
registerReceiver(receiver, IntentFilter(action))
}
private fun countdownTimer() {
binding.pbNext.visibility = View.VISIBLE
binding.icNext.visibility = View.GONE
object : CountDownTimer(15000, 1000) {
override fun onTick(millisUntilFinished: Long) {
binding.tvCount.setText("Resend Code in : " + millisUntilFinished / 1000)
}
override fun onFinish() {
binding.tvCount.setText("I didn`t receive a code")
binding.icNext.visibility = View.VISIBLE
binding.pbNext.visibility = View.GONE
}
}.start()
}
override fun onClick(view: View) {
when (view.id) {
R.id.btn_next -> {
if (binding.otpView.text.toString().length == 6) {
LocalSharedPreference.getInstance(this#OTPNewActivity).isPhoneNumberVerified =
true
verifyPhoneNumberWithCode(systemGeneratedCode, userEnteredCode)
}
}
R.id.tv_count -> {
if (binding.tvCount.text.equals(this#OTPNewActivity.resources.getString(R.string.i_dont_received_code)))
resendVerificationCode(phoneNumer, resendToken)
}
R.id.ic_back -> {
finish()
}
}
}
private fun resendVerificationCode(
phoneNumber: String,
token: PhoneAuthProvider.ForceResendingToken?
) {
val optionsBuilder = PhoneAuthOptions.newBuilder(auth)
.setPhoneNumber(phoneNumber) // Phone number to verify
.setTimeout(15L, TimeUnit.SECONDS) // Timeout and unit
.setActivity(this) // Activity (for callback binding)
.setCallbacks(callbacks) // OnVerificationStateChangedCallbacks
if (token != null) {
optionsBuilder.setForceResendingToken(token) // callback's ForceResendingToken
}
PhoneAuthProvider.verifyPhoneNumber(optionsBuilder.build())
}
var receiver: BroadcastReceiver = object : BroadcastReceiver() {
override fun onReceive(context: Context?, intent: Intent) {
if (intent.action == "android.provider.Telephony.SMS_RECEIVED") {
val bundle = intent.extras
var msgs: Array<SmsMessage?>? = null
var msg_from: String? = ""
Log.d(TAG, "onReceive called ")
if (bundle != null) {
try {
val pdus = bundle["pdus"] as Array<Any>?
msgs = arrayOfNulls(pdus!!.size)
for (i in msgs.indices) {
msgs[i] = SmsMessage.createFromPdu(pdus[i] as ByteArray)
msg_from = msgs[i]!!.getOriginatingAddress()
val msgBody: String = msgs[i]!!.getMessageBody()
if (msgBody.contains("is your verification code for running-errands.firebaseapp.com")) {
val _1: Char = msgBody[0]
val _2: Char = msgBody[1]
val _3: Char = msgBody[2]
val _4: Char = msgBody[3]
val _5: Char = msgBody[4]
val _6: Char = msgBody[5]
val code: String =
_1.toString() + _2.toString() + _3.toString() + _4.toString() + _5.toString() + _6.toString()
// binding.otpView.text = SpannableStringBuilder(code!!)
binding.otpView.setText(code)
verifyPhoneNumberWithCode(systemGeneratedCode, code!!)
}
}
} catch (e: Exception) {
e.printStackTrace()
}
}
}
}
}
override fun onDestroy() {
super.onDestroy()
unregisterReceiver(receiver);
}
}
I'm implementing a small BLE app and I have a list of devices and I want to turn them off when I press on them in the list view.
My service UUID is:0xFFB0
My characteristic UUID is:0xFFB7 Write with response/ Read with response
Characteristic format:
Byte order ; 0 ; 1~19
R/W ON/OFF ; -
0x01 -> ON and 0x00 -> OFF
class MainActivity : AppCompatActivity() {
private var macList: ArrayList<String> = ArrayList()
private var deviceList: ArrayList<BluetoothDevice> = ArrayList()
private lateinit var adapter: ArrayAdapter<String>
private var mBtAdapter: BluetoothAdapter? = null
private var mConnectedGatt: BluetoothGatt? = null
private var mCharacteristic: BluetoothGattCharacteristic? = null
private var on = true
private lateinit var currentDevice: BluetoothDevice
/**
* CHECK IF BT AND LOCATION IS ON!
*/
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
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
list_view.onItemClickListener = AdapterView.OnItemClickListener { _, _, position, _ ->
currentDevice = deviceList[position]
mConnectedGatt = currentDevice.connectGatt(applicationContext, false, gattCallback)
}
scan_button.setOnClickListener {
scanForDeviceWithFilter(LIGHT_SERVICE)
}
power_button.setOnClickListener {
mConnectedGatt = currentDevice.connectGatt(applicationContext, true, powerCallback)
on = !on
}
}
override fun onStop() {
super.onStop()
//Disconnect from any active tag connection
if (mConnectedGatt != null) {
mConnectedGatt!!.disconnect()
mConnectedGatt = null
}
}
private fun scanForDeviceWithFilter(serviceUUID: Int) {
val uuid = ParcelUuid(convertFromInteger(serviceUUID))
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)
Thread.sleep(3000)
mBtAdapter!!.bluetoothLeScanner.stopScan(scanDevicesCallback)
}
private val scanDevicesCallback = object : ScanCallback() {
override fun onBatchScanResults(results: MutableList<ScanResult>?) {
results?.forEach { result ->
macList.add(result.device.toString())
Log.d(TAG, "device name:${result.device}")
}
}
override fun onScanResult(callbackType: Int, result: ScanResult?) {
result?.let {
if (!macList.contains(result.device.name.toString())) {
deviceList.add(result.device)
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 val gattCallback = object : BluetoothGattCallback() {
/* OK */
override fun onConnectionStateChange(gatt: BluetoothGatt, status: Int, newState: Int) {
Log.d(TAG, "Connection State Change: " + status + " -> " + connectionState(newState))
if (status == BluetoothGatt.GATT_SUCCESS && newState == BluetoothProfile.STATE_CONNECTED) {
/**
* Once successfully connected, we must next discover all the services on the
* device before we can read and write their characteristics.
*/
gatt.discoverServices()
Thread.sleep(500)
} else if (status != BluetoothGatt.GATT_SUCCESS) {
/**
* If there is a failure at any stage, simply disconnect
*/
gatt.disconnect()
}
}
private fun connectionState(status: Int): String {
return when (status) {
BluetoothProfile.STATE_CONNECTED -> "Connected"
BluetoothProfile.STATE_DISCONNECTED -> "Disconnected"
BluetoothProfile.STATE_CONNECTING -> "Connecting"
BluetoothProfile.STATE_DISCONNECTING -> "Disconnecting"
else -> status.toString()
}
}
override fun onServicesDiscovered(gatt: BluetoothGatt?, status: Int) {
mCharacteristic = gatt?.getService(convertFromInteger(LIGHT_SERVICE))?.getCharacteristic(convertFromInteger(PASSWORD_CHARACTERISTIC))
mCharacteristic!!.setValue("0123")
if (gatt!!.writeCharacteristic(mCharacteristic)) {
Log.d(TAG, "Login success")
Thread.sleep(500)
} else
Log.d(TAG, "Login failed")
}
override fun onCharacteristicWrite(gatt: BluetoothGatt?, characteristic: BluetoothGattCharacteristic?, status: Int) {
if (status != BluetoothGatt.GATT_SUCCESS) {
Log.d("onCharacteristicWrite", "Failed write, retrying: $status")
gatt!!.writeCharacteristic(characteristic)
}
super.onCharacteristicWrite(gatt, characteristic, status)
}
}
private val powerCallback = object : BluetoothGattCallback() {
/* OK */
override fun onConnectionStateChange(gatt: BluetoothGatt, status: Int, newState: Int) {
Log.d(TAG, "Connection State Change: " + status + " -> " + connectionState(newState))
if (status == BluetoothGatt.GATT_SUCCESS && newState == BluetoothProfile.STATE_CONNECTED) {
gatt.discoverServices()
Thread.sleep(500)
} else if (status != BluetoothGatt.GATT_SUCCESS) {
gatt.disconnect()
}
}
private fun connectionState(status: Int): String {
return when (status) {
BluetoothProfile.STATE_CONNECTED -> "Connected"
BluetoothProfile.STATE_DISCONNECTED -> "Disconnected"
BluetoothProfile.STATE_CONNECTING -> "Connecting"
BluetoothProfile.STATE_DISCONNECTING -> "Disconnecting"
else -> status.toString()
}
}
override fun onServicesDiscovered(gatt: BluetoothGatt?, status: Int) {
if (on) {
mCharacteristic = gatt!!.getService(convertFromInteger(LIGHT_SERVICE))?.getCharacteristic(convertFromInteger(LIGHT_CHARACTERISTIC))
mCharacteristic!!.setValue("0")
gatt.setCharacteristicNotification(mCharacteristic, true)
if (gatt.writeCharacteristic(mCharacteristic)) {
Log.d(TAG, "Power off success")
Thread.sleep(500)
} else Log.d(TAG, "Power off failed")
} else {
mCharacteristic = gatt!!.getService(convertFromInteger(LIGHT_SERVICE))?.getCharacteristic(convertFromInteger(LIGHT_CHARACTERISTIC))
mCharacteristic!!.setValue("1")
if (gatt.writeCharacteristic(mCharacteristic)) {
Log.d(TAG, "Power on success")
Thread.sleep(500)
} else Log.d(TAG, "Power on failed")
}
}
override fun onCharacteristicWrite(gatt: BluetoothGatt?, characteristic: BluetoothGattCharacteristic?, status: Int) {
if (status != BluetoothGatt.GATT_SUCCESS) {
Log.d("onCharacteristicWrite", "Failed write, retrying: $status")
gatt!!.writeCharacteristic(characteristic)
}
super.onCharacteristicWrite(gatt, characteristic, status);
}
}
private fun isBLESupported(context: Context): Boolean {
return BluetoothAdapter.getDefaultAdapter() != null && context.packageManager.hasSystemFeature(PackageManager.FEATURE_BLUETOOTH_LE)
}
init {
mBtAdapter = BluetoothAdapter.getDefaultAdapter()
}
private fun checkBTPermissions() {
val permissionCheck = checkSelfPermission("Manifest.permission.ACCESS_FINE_LOCATION")
if (permissionCheck != 0) {
requestPermissions(arrayOf(Manifest.permission.ACCESS_FINE_LOCATION), 1001)
}
}
fun convertFromInteger(i: Int): UUID? {
val msb = 0x0000000000001000L
val lsb = -0x7fffff7fa064cb05L
val value = (i and ((-0x1).toLong()).toInt()).toLong()
return UUID(msb or (value shl 32), lsb)
}
companion object {
private const val TAG = "Main Activity"
private const val LIGHT_SERVICE = 0xffb0
private const val LIGHT_CHARACTERISTIC = 0xffb7
private const val PASSWORD_CHARACTERISTIC = 0xffba
private const val REQUEST_ENABLE_BLUETOOTH = 1
}
}
I changed my code, but now when I try to write I get status = 128; GATT_NO_RESOURCES and I have no clue what to do.
I'm not sure if you have understand the flow correctly.
The onCharacteristicWrite is not called "to send data to the BLE device". It is being called after you have called writeCharacteristic, after the remote device has responded.
This callback is called when you are trying to send data using writeCharacteristic(characteristics) and the BLE device responds with some value.
This is not really correct about onCharacteristicChanged. This method is called whenever the local device receives a notification from the remote device. To enable this you must first tell the local Bluetooth stack to forward the notifications to you by calling setCharacteristicNotification first, and write the client characteristic configuration descriptor to the remote device so it will send notifications.
After you have called writeDescriptor, the onDescriptorWrite callback will be called when the remote device responds.
What you should do is to implement the onServicesDiscovered method which will be called as a result of discoverServices, when the service discovery is complete. In this callback you can call writeCharacteristic on the ON/OFF characteristic.
From your description it doesn't seem you need to receive notifications.
I solved my problem. I was sending a string to the device, not a byte value.
mCharacteristic!!.value = byteArrayOf(0x00) fixed my problem.
I am currently trying to work with Google Fit API. I have been mainly by following Google's documentation.
Below is the code that I have which seems to have a problem
The problem I have is that it shows me all step count. I want to return my app today step count. this code return me 550530 steps. I want to show today's step count.
class MainActivity : AppCompatActivity(), OnDataPointListener,
GoogleApiClient.ConnectionCallbacks,
GoogleApiClient.OnConnectionFailedListener {
private var authInProgress = false
private var mApiClient: GoogleApiClient? = null
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
if (savedInstanceState != null) {
authInProgress = savedInstanceState.getBoolean(AUTH_PENDING)
}
mApiClient = GoogleApiClient.Builder(this)
.addApi(Fitness.SENSORS_API)
.addScope(Scope(Scopes.FITNESS_ACTIVITY_READ_WRITE))
.addConnectionCallbacks(this)
.addOnConnectionFailedListener(this)
.build()
}
override fun onStart() {
super.onStart()
mApiClient!!.connect()
}
override fun onStop() {
super.onStop()
Fitness.SensorsApi.remove(mApiClient, this)
.setResultCallback { status ->
if (status.isSuccess) {
mApiClient!!.disconnect()
}
}
}
private fun registerFitnessDataListener(dataSource: DataSource, dataType: DataType) {
val request = SensorRequest.Builder()
.setDataSource(dataSource)
.setDataType(dataType)
.setSamplingRate(3, TimeUnit.SECONDS)
.build()
Fitness.SensorsApi.add(mApiClient, request, this).setResultCallback { status ->
if (status.isSuccess) {
Log.e("GoogleFit", "SensorApi successfully added")
} else {
Log.e(
"GoogleFit",
"adding status: " + status.statusMessage
)
}
}
}
override fun onConnected(bundle: Bundle?) {
val dataSourceRequest = DataSourcesRequest.Builder()
.setDataTypes(DataType.TYPE_STEP_COUNT_CUMULATIVE)
.setDataSourceTypes(DataSource.TYPE_RAW)
.build()
val dataSourcesResultCallback =
ResultCallback<DataSourcesResult> { dataSourcesResult ->
for (dataSource in dataSourcesResult.dataSources) {
if (DataType.TYPE_STEP_COUNT_CUMULATIVE == dataSource.dataType) {
registerFitnessDataListener(
dataSource,
DataType.TYPE_STEP_COUNT_CUMULATIVE
)
}
}
}
Fitness.SensorsApi.findDataSources(
mApiClient,
dataSourceRequest
)
.setResultCallback(dataSourcesResultCallback)
}
override fun onConnectionFailed(connectionResult: ConnectionResult) {
if (!authInProgress) {
try {
authInProgress = true
connectionResult.startResolutionForResult(
this,
REQUEST_OAUTH
)
} catch (e: IntentSender.SendIntentException) {
Log.e("GoogleFit", "sendingIntentException " + e.message)
}
} else {
Log.e("GoogleFit", "authInProgress")
}
}
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
//++++++++
super.onActivityResult(requestCode, resultCode, data)
if (requestCode == REQUEST_OAUTH) {
authInProgress = false
if (resultCode == Activity.RESULT_OK) {
if (!mApiClient!!.isConnecting && !mApiClient!!.isConnected) {
mApiClient!!.connect()
}
} else if (resultCode == Activity.RESULT_CANCELED) {
Log.e("GoogleFit", "RESULT_CANCELED")
}
} else {
Log.e("GoogleFit", "requestCode NOT request_oauth")
}
}
override fun onConnectionSuspended(i: Int) {}
override fun onDataPoint(dataPoint: DataPoint) {
for (field in dataPoint.dataType.fields) {
val value = dataPoint.getValue(field)
runOnUiThread {
Toast.makeText(
applicationContext,
"Field: " + field.name + " Value: " + value,
Toast.LENGTH_SHORT
).show()
}
}
}
companion object {
private const val REQUEST_OAUTH = 1
private const val AUTH_PENDING = "auth_state_pending"
}
}
The code is returning 550530 steps because you are using DataType.TYPE_STEP_COUNT_CUMULATIVE, CUMULATIVE will return the step count data as a sum since the start of the count or you can say from the beginning.
You can use DataType.TYPE_STEP_COUNT_DELTA it will give you each data point not the sum of all.
You can check more in the documentation from DataType
Happy Coding!!
i am creating an app that scans for and pairs to Bluetooth devices. i am displaying the devices in a RecyclerView and indicating bond state by coloring the ViewHolder for that device. my problem is that the color of the ViewHolder is only changed after scanning for devices again and i want it to immediately update the color on pair or unpair. i am attempting to do this through use of a broadcast receiver but i am unable to get a reference to the correct ViewHolder. how can i achieve this? i am including my code below for my RecyclerView.Adapter and my BluetoothUtils file containing the broadcast receiver. thanks in advance. my adapter:
class DeviceAdapter(val mContext : Context) : RecyclerView.Adapter<DeviceAdapter.DeviceHolder>() {
companion object {
val TAG = "Device Adapter"
fun DeviceHolder.setColor(bonded: Boolean):Unit{
val background = if (bonded)Color.CYAN else Color.TRANSPARENT
this.itemView.setBackgroundColor(background)
}
}
val mDevices = ArrayList<BluetoothDevice>()
fun updateItems(list: ArrayList<BluetoothDevice>) {
mDevices.clear()
mDevices.addAll(list)
Log.d(TAG, "updating items : $mDevices")
notifyDataSetChanged()
}
fun ViewGroup.inflate(#LayoutRes res: Int, attachToRoot: Boolean = false): View {
return LayoutInflater.from(mContext).inflate(res, this, attachToRoot)
}
override fun onBindViewHolder(holder: DeviceHolder, position: Int) {
Log.d(TAG, "onBindViewHolder called!")
holder.bindItems(mDevices.get(position))
if (mDevices.get(position).bondState==BluetoothDevice.BOND_BONDED) {
holder.itemView.setBackgroundColor(CYAN)
} else {
holder.itemView.setBackgroundColor(Color.TRANSPARENT)
}
}
override fun onCreateViewHolder(parent: ViewGroup?, viewType: Int): DeviceAdapter.DeviceHolder {
Log.d(TAG, "onCreateViewHolder called!")
val v = parent!!.inflate(R.layout.device_item, false)
return DeviceHolder(v)
}
override fun getItemCount(): Int {
return mDevices.size
}
inner class DeviceHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
val nameView = itemView.findViewById(R.id.nameView) as TextView
val addrView = itemView.findViewById(R.id.addressView) as TextView
var dialog: AlertDialog? = null;
fun bindItems(btDevice: BluetoothDevice) {
Log.d(TAG, "holder created!")
nameView.text = btDevice.name ?: "Unknown"
addrView.text = btDevice.address
itemView.setOnClickListener {
dialog = AlertDialog.Builder(it.context)
.setTitle("Options")
.setView(R.layout.options_dialog_layout)
.setNegativeButton("Cancel", DialogInterface.OnClickListener { _, which -> })
.create()
dialog!!.show()
val ops = listOf(
dialog!!.findViewById(R.id.statOp),
dialog!!.findViewById(R.id.pairOp),
dialog!!.findViewById(R.id.connectOp),
dialog!!.findViewById(R.id.sendOp),
dialog!!.findViewById(R.id.unPairOp)
)
ops.forEach { it.setOnClickListener {
Toast.makeText(it.context, it.id.toString(), Toast.LENGTH_SHORT).show()
when(it.id){
R.id.statOp -> {}
R.id.connectOp -> {
Log.d(TAG, "connectOp reached")
BluetoothReflection.connectDevice(btDevice)
dialog!!.dismiss()
}// BluetoothUtils.connect(BluetoothAdapter.getDefaultAdapter(), btDevice)
R.id.pairOp -> {
Log.d(TAG, "pairOp reached")
BluetoothUtils.startPair(BluetoothAdapter.getDefaultAdapter(), btDevice)
if (btDevice.bondState==BluetoothDevice.BOND_BONDED){
this#DeviceHolder.itemView.setBackgroundColor(CYAN) //doesn't work
}
Log.d(TAG, "start pair complete")
dialog!!.dismiss()
}//
R.id.unPairOp -> {//no executable code found here
Log.d(TAG, "unPairOp reached")
BluetoothUtils.unPair(btDevice)
if (btDevice.bondState==BluetoothDevice.BOND_NONE){
this#DeviceHolder.itemView.setBackgroundColor(Color.TRANSPARENT) //doesn't work
}
Log.d(TAG, "unpair complete")
dialog!!.dismiss()
}
R.id.sendOp -> {}
}
} }
}
}
}
}
and my BluetoothUtils:
class BluetoothUtils {
companion object {
var listener: ListenThread? = null
val _UUID = UUID.fromString("a0e7e4c7-0e4e-43b7-9d18-659192512164")
val TAG = "BluetoothUtils"
val receiver = MainBTStatusReceiver()
fun initPairingServer(adapter: BluetoothAdapter){
var mmServerSocket: BluetoothServerSocket?
try {
var tmp = adapter.listenUsingRfcommWithServiceRecord(TAG, _UUID)
mmServerSocket = tmp
listener = ListenThread(mmServerSocket)
listener!!.start()
}catch (ioe: IOException){
Log.e(TAG, "Error initializing Bluetooth", ioe)
}
}
fun cancelListener() = listener!!.cancel()
fun connect(adapter: BluetoothAdapter, device: BluetoothDevice){
var btSocket: BluetoothSocket?
try {
adapter.cancelDiscovery()
btSocket = device.createRfcommSocketToServiceRecord(_UUID)
PairingThread(btSocket).start()
}catch (ioe: IOException){
Log.e(TAG, "error connecting", ioe)
}
}
fun startPair(adapter: BluetoothAdapter, device: BluetoothDevice): Unit{
adapter.cancelDiscovery()
Log.d(TAG, device.bondState.toString())
device.createBond()
}
fun unPair(device: BluetoothDevice): Any = device::class.java.getMethod("removeBond").invoke(device)
}
}
class ListenThread(val btServSock: BluetoothServerSocket) : Thread(){
companion object {
val TAG = "ListenThread"
}
var btSocket: BluetoothSocket? = null
override fun run() {
super.run()
while (true){
try {
Log.d(TAG, "listening . . . ")
btSocket = btServSock.accept()
}catch (ioe: IOException){
Log.e(TAG, "Error", ioe) // SHOULD HANDLE FAILURE OF LISTENER INSTANTIATION
break
}
//manage connection here
//with either BluetoothUtils function
//or BluetoothSocket extension
}
}
fun cancel() = btServSock.close()
}
class PairingThread(val btSocket: BluetoothSocket) : Thread(){
companion object {
val TAG = "Pairing Thread"
}
override fun run() {
super.run()
try {
Log.d(TAG, "attempting to connect")
btSocket.connect()
}catch (ioe: IOException){
Log.e(TAG, "error connecting", ioe)
btSocket.close()
}
}
}
class MainBTStatusReceiver(): BroadcastReceiver(){
val TAG = "MainBTStatusReceiver"
var mAdapter: DeviceAdapter? = null
fun setAdapter(adapter: DeviceAdapter){
mAdapter = adapter
}
override fun onReceive(context: Context?, intent: Intent) {
val action = intent.action
val devExtra = intent.getParcelableExtra<BluetoothDevice>(BluetoothDevice.EXTRA_DEVICE) as BluetoothDevice
when(action){
BluetoothDevice.ACTION_BOND_STATE_CHANGED -> {
val device = intent.getParcelableExtra<BluetoothDevice>(BluetoothDevice.EXTRA_DEVICE)
when(device.bondState){
BluetoothDevice.BOND_BONDED -> {
Log.d(TAG, "BONDED")
}
BluetoothDevice.BOND_BONDING -> {Log.d(TAG, "BONDING")}
BluetoothDevice.BOND_NONE -> {Log.d(TAG, "NONE")}
}
}
}
}
Add boolean or int to BluetoothDevice model for managing view.
For example,
BluetoothDevice: added isOn state. (Sorry, it's Java)
class BluetoothDevice {
boolean isOn;
public boolean isOn() {
return isOn;
}
public void setOn(boolean isOn) {
this.isOn = isOn;
}
}
DeviceHolder: changed color of view
fun bindItems(btDevice: BluetoothDevice) {
stateView.textColor = btDevice.isOn() ? Color.RED : Color.GREEN
}
DeviceAdapter: added getItems
fun getItems() {
return mDevices
}
If you want to change isOn state, change model and notify it.
adapter.getItems().get(i).setOn(true);
adapter.notifyDataSetChanged();
I like the above answer as well but the way that i got this done was to pass the BroadcastReceiver into the DeviceAdapter :
class DeviceAdapter(val mContext:Context, val mReceiver:MainBTStatusReceiver) : RecyclerView.Adapter<DeviceAdapter.DeviceHolder>()
and then made a ViewHolder a member of the BroadcastReceiver and a setter function for the ViewHolder named setFocus. before calling any functions from the BluetoothUtils class i called the setFocus function and then the broadcast receiver modifies the color of the view that focus is currently set too. I do have some concern that this might not be reliable as the most accurate method to modify the correct ViewHolder every time. if you see any problem with this please comment to let me know.
my updated BroadcastReceiver:
class MainBTStatusReceiver(): BroadcastReceiver() {
val TAG = "MainBTStatusReceiver"
var holder: DeviceAdapter.DeviceHolder? = null
fun setFocus(holder: DeviceAdapter.DeviceHolder) {
this.holder = holder
}
override fun onReceive(context: Context?, intent: Intent) {
val action = intent.action
when (action) {
BluetoothDevice.ACTION_BOND_STATE_CHANGED -> {
val device = intent.getParcelableExtra<BluetoothDevice>(BluetoothDevice.EXTRA_DEVICE)
when (device.bondState) {
BluetoothDevice.BOND_BONDED -> {
holder!!.itemView.setBackgroundColor(Color.CYAN)
Log.d(TAG, "BONDED")
}
BluetoothDevice.BOND_BONDING -> {
Log.d(TAG, "BONDING")
}
BluetoothDevice.BOND_NONE -> {
holder!!.itemView.setBackgroundColor(Color.TRANSPARENT)
Log.d(TAG, "NONE")
}
}
}
}
}
}
and the two statements in my when expression that call setFocus():
R.id.pairOp -> {
Log.d(TAG, "pairOp reached")
mReceiver.setFocus(this#DeviceHolder)
BluetoothUtils.startPair(BluetoothAdapter.getDefaultAdapter(), btDevice)
Log.d(TAG, "start pair complete")
dialog!!.dismiss()
}
R.id.unPairOp -> {
Log.d(TAG, "unPairOp reached")
mReceiver.setFocus(this#DeviceHolder)
BluetoothUtils.unPair(btDevice)
Log.d(TAG, "unpair complete")
dialog!!.dismiss()
}