XYOracleNetwork/sdk-ble-android

View on GitHub
ble-android-sample/src/main/kotlin/network/xyo/ble/sample/fragments/InfoFragment.kt

Summary

Maintainability
F
4 days
Test Coverage
@file:Suppress("SpellCheckingInspection")

package network.xyo.ble.sample.fragments

import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.View.GONE
import android.view.View.VISIBLE
import android.view.ViewGroup
import android.widget.CompoundButton
import kotlinx.coroutines.*
import network.xyo.ble.devices.apple.XYIBeaconBluetoothDevice
import network.xyo.ble.devices.xy.XY3BluetoothDevice
import network.xyo.ble.devices.xy.XY4BluetoothDevice
import network.xyo.ble.devices.xy.XYFinderBluetoothDevice
import network.xyo.ble.generic.devices.XYBluetoothDevice
import network.xyo.ble.generic.listeners.XYBluetoothDeviceListener
import network.xyo.ble.generic.gatt.peripheral.XYBluetoothResultErrorCode
import network.xyo.ble.sample.R
import network.xyo.ble.sample.XYDeviceData
import network.xyo.ble.sample.databinding.FragmentInfoBinding
import network.xyo.ble.generic.gatt.peripheral.ble

class InfoFragment(device: XYBluetoothDevice, deviceData : XYDeviceData) : XYDeviceFragment<FragmentInfoBinding>(device, deviceData), View.OnClickListener, CompoundButton.OnCheckedChangeListener {

    override fun inflate(inflater: LayoutInflater, container: ViewGroup?): FragmentInfoBinding {
        return FragmentInfoBinding.inflate(inflater, container, false)
    }

    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        super.onViewCreated(view, savedInstanceState)

        binding.buttonStartTest.setOnClickListener(this)
        binding.buttonConnect.setOnClickListener(this)
        binding.buttonDisconnect.setOnClickListener(this)
        binding.buttonFind.setOnClickListener(this)
        binding.buttonStopFind.setOnClickListener(this)
        binding.buttonStayAwake.setOnClickListener(this)
        binding.buttonFallAsleep.setOnClickListener(this)
        binding.buttonLock.setOnClickListener(this)
        binding.buttonUnlock.setOnClickListener(this)
        binding.buttonEnableNotify.setOnClickListener(this)
        binding.buttonStayConnected.setOnCheckedChangeListener(this)

        when (device) {
            is XY4BluetoothDevice -> {
                binding.buttonEnableNotify.visibility = VISIBLE
                binding.buttonDisableNotify.visibility = VISIBLE
            }
            is XY3BluetoothDevice -> {
                binding.buttonEnableNotify.visibility = VISIBLE
                binding.buttonDisableNotify.visibility = VISIBLE
            }

            else -> {
                binding.buttonEnableNotify.visibility = GONE
                binding.buttonDisableNotify.visibility = GONE
            }
        }
    }

    override fun onResume() {
        super.onResume()
        log.info("onResume: InfoFragment")
        updateAdList()
        updateUI()

        device.reporter.addListener("info", object: XYBluetoothDeviceListener() {
            override fun entered(device: XYBluetoothDevice) {
                super.entered(device)
                log.info("Entered")
            }

            override fun detected(device: XYBluetoothDevice) {
                super.detected(device)
                updateUI()
            }

            override fun connectionStateChanged(device: XYBluetoothDevice, newState: Int) {
                super.connectionStateChanged(device, newState)
                updateUI()
            }

            override fun exited(device: XYBluetoothDevice) {
                super.exited(device)
                log.info("Exited")
            }
        })
    }

    override fun onPause() {
        super.onPause()
        device.reporter.removeListener("info")
    }

    override fun update() {
        updateUI()
    }

    private fun updateUI() {
        activity?.runOnUiThread {
            log.info("update")

            binding.textFamily.text = device.name
            binding.textRssi.text = device.rssi.toString()

            (device as? XYIBeaconBluetoothDevice)?.let {
                binding.textPower.text = it.power.toString()
                binding.textMajor.text = String.format(getString(R.string.hex_placeholder), it.major.toInt().toString(16))
                binding.textMinor.text = String.format(getString(R.string.hex_placeholder), it.minor.toInt().toString(16))
            }

            binding.textPulseCount.text = device.detectCount.toString()
            binding.textEnterCount.text = device.enterCount.toString()
            binding.textExitCount.text = device.exitCount.toString()
            binding.textAvgGapSize.text = device.averageDetectGap.toString()
            binding.textLastGapSize.text = device.lastDetectGap.toString()
            binding.textMaxGapSize.text = device.maxDetectTime.toString()

            if (device.connected) {
                binding.buttonConnect.visibility = GONE
                binding.buttonDisconnect.visibility = VISIBLE
            } else {
                binding.buttonConnect.visibility = VISIBLE
                binding.buttonDisconnect.visibility = GONE
            }
        }
    }

    override fun onCheckedChanged(buttonView: CompoundButton?, isChecked: Boolean) {
        when (buttonView?.id) {
            R.id.button_stayConnected -> {
                ble.launch {
                    device.stayConnected = isChecked
                    updateUI()
                }
            }
        }
    }

    override fun onClick(view: View?) {
        when (view?.id) {
            R.id.button_connect -> {
                connect()
            }
            R.id.button_disconnect -> {
                disconnect()
            }
            R.id.button_find -> {
                find()
            }
            R.id.button_stop_find -> {
                stopFind()
            }
            R.id.button_stay_awake -> {
                wake()
            }
            R.id.button_fall_asleep -> {
                sleep()
            }
            R.id.button_lock -> {
                lock()
            }
            R.id.button_unlock -> {
                unlock()
            }
            R.id.button_enable_notify -> {
                enableButtonNotify(true)
            }
            R.id.button_disable_notify -> {
                enableButtonNotify(false)
            }
            R.id.button_startTest -> {
                //testRefreshGatt()
            }
        }
    }

    /*private fun testRefreshGatt() {
        ble.launch {
            val connection = device?.connectGatt()?.await()
            val device: XYBluetoothDevice? = activity?.device
            val connection = device?.connect()?.await()
            if (connection?.error != null) {
                showToast("Connection failed")
                return@launch
            }
            val result = device?.refreshGatt()?.await()
            showToast(result.toString())
        }
    }*/

    private fun connect() {
        ble.launch {
            val result = device.connect()
            val error = result.error
            if (error != XYBluetoothResultErrorCode.None) {
                log.error(error.toString())
            } else {
                log.info("Connected")
            }
            updateUI()
        }
    }

    private fun disconnect() {
        ble.launch {
            device.disconnectAsync().await()
            updateUI()
        }
    }

    private fun find() {
        log.info("beepButton: got xyDevice")
        activity?.runOnUiThread {
            binding.buttonFind.isEnabled = false
        }

        ble.launch {
            (device as? XYFinderBluetoothDevice)?.find()
            activity?.runOnUiThread {
                this@InfoFragment.isVisible.let { binding.buttonFind.isEnabled = true }

            }
        }
    }

    private fun stopFind() {
        log.info("beepButton: got xyDevice")
        activity?.runOnUiThread {
            binding.buttonFind.isEnabled = false
        }

        ble.launch {
            (device as? XYFinderBluetoothDevice)?.stopFind()
            activity?.runOnUiThread {
                this@InfoFragment.isVisible.let { binding.buttonFind.isEnabled = true }
            }
        }
    }

    private fun wake() {
        log.info("stayAwakeButton: onClick")
        activity?.runOnUiThread {
            binding.buttonStayAwake.isEnabled = false
        }

        ble.launch {
            val stayAwake = (device as? XYFinderBluetoothDevice)?.stayAwake()
            if (stayAwake == null) {
                log.info("Stay Awake Failed to Complete Call")
            } else {
                log.info("Stay Awake Set")
            }
            activity?.runOnUiThread {
                this@InfoFragment.isVisible.let { binding.buttonStayAwake.isEnabled = true }
            }
        }
    }

    private fun sleep() {
        log.info("fallAsleepButton: onClick")
        activity?.runOnUiThread {
            binding.buttonFallAsleep.isEnabled = false
        }

        ble.launch {
            val fallAsleep = (device as? XYFinderBluetoothDevice)?.fallAsleep()
            if (fallAsleep == null) {
                log.error("Fall Asleep Failed to Complete Call")
            } else {
                log.info("Fall Asleep Set")
            }
            activity?.runOnUiThread {
                this@InfoFragment.isVisible.let { binding.buttonFallAsleep.isEnabled = true }
            }
        }
    }

    private fun lock() {
        log.info("lockButton: onClick")
        activity?.runOnUiThread {
            binding.buttonLock.isEnabled = false
        }

        ble.launch {
            val locked = (device as? XYFinderBluetoothDevice)?.lock()
            when {
                locked == null -> log.error("Device does not support Lock")
                locked.error == XYBluetoothResultErrorCode.None -> {
                    log.info("Locked: ${locked.value}")
                    updateStayAwakeEnabledStates()
                }
                else -> log.error("Lock Error: ${locked.error}")
            }
            activity?.runOnUiThread {
                this@InfoFragment.isVisible.let { binding.buttonLock.isEnabled = true }
            }
        }
    }

    private fun unlock() {
        log.info("unlockButton: onClick")
        activity?.runOnUiThread {
            binding.buttonUnlock.isEnabled = false
        }

        ble.launch {
            val unlocked = (device as? XYFinderBluetoothDevice)?.unlock()
            when {
                unlocked == null -> log.error("Device does not support Unlock")
                unlocked.error == XYBluetoothResultErrorCode.None -> {
                    log.info("Unlocked: ${unlocked.value}")
                    updateStayAwakeEnabledStates()
                }
                else -> log.error("Unlock Error: ${unlocked.error}")
            }
            activity?.runOnUiThread {
                this@InfoFragment.isVisible.let { binding.buttonUnlock.isEnabled = true }
            }
        }
    }


    private suspend fun updateStayAwakeEnabledStates() {
        return ble.async {
            log.info("updateStayAwakeEnabledStates")
            val xy4 = device as? XY4BluetoothDevice
            if (xy4 != null) {
                val stayAwake = xy4.primary.stayAwake.get()
                log.info("updateStayAwakeEnabledStates: ${stayAwake.value}")
                activity?.runOnUiThread {
                    this@InfoFragment.isVisible.let {
                        if (stayAwake.value != 0x0.toUByte()) {
                            binding.buttonFallAsleep.isEnabled = true
                            binding.buttonStayAwake.isEnabled = false
                        } else {
                            binding.buttonFallAsleep.isEnabled = false
                            binding.buttonStayAwake.isEnabled = true
                        }
                    }
                }
            } else {
                log.error("updateStayAwakeEnabledStates: Not an XY4!", false)
            }
            return@async
        }.await()
    }

    private fun enableButtonNotify(enable: Boolean) {
        ble.launch {
            val xy4 = device as? XY4BluetoothDevice
            if (xy4 != null) {
                val notify = xy4.primary.buttonState.enableNotify(enable)
                log.info(notify.toString())

            } else {
                val xy3 = device as? XY3BluetoothDevice
                if (xy3 != null) {
                    val notify = xy3.controlService.button.enableNotify(enable)
                    log.info(notify.toString())
                }
            }
        }
    }

    private fun updateAdList() {
        activity?.runOnUiThread {
            var txt = ""
            device.let { device ->
                for (i in 0 until device.ads.size()) {
                    val key = device.ads.keyAt(i)
                    txt = txt + device.ads[key].data?.toHex() + "\r\n"
                }
                binding.adList.text = txt
            }
        }
    }

    private val hexChars = "0123456789ABCDEF".toCharArray()
    private fun ByteArray.toHex(): String {
        val result = StringBuffer()

        forEach {
            val octet = it.toInt()
            val firstIndex = (octet and 0xF0).ushr(4)
            val secondIndex = octet and 0x0F
            result.append(hexChars[firstIndex])
            result.append(hexChars[secondIndex])
        }

        return result.toString()
    }
}