adding bluetooth

master
Zhengyu Peng 2 years ago
parent e745dabe6e
commit 4e66b26b8b

@ -1,6 +1,8 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.rookiedev.hexapod">
<uses-permission android:name="android.permission.BLUETOOTH_CONNECT" />
<uses-permission android:name="android.permission.INTERNET" />
<application

@ -0,0 +1,540 @@
package com.rookiedev.hexapod.network
import android.Manifest
import android.annotation.SuppressLint
import android.bluetooth.BluetoothAdapter
import android.bluetooth.BluetoothDevice
import android.bluetooth.BluetoothServerSocket
import android.bluetooth.BluetoothSocket
import android.content.Context
import android.content.pm.PackageManager
import android.os.Bundle
import android.os.Handler
import androidx.core.app.ActivityCompat
import java.io.IOException
import java.io.InputStream
import java.io.OutputStream
import java.util.*
/**
* This class does all the work for setting up and managing Bluetooth
* connections with other devices. It has a thread that listens for
* incoming connections, a thread for connecting with a device, and a
* thread for performing data transmissions when connected.
*/
class BluetoothChatService(context: Context?, handler: Handler) {
// Member fields
private val mAdapter: BluetoothAdapter
private val mHandler: Handler
private var mSecureAcceptThread: AcceptThread? = null
private var mInsecureAcceptThread: AcceptThread? = null
private var mConnectThread: ConnectThread? = null
private var mConnectedThread: ConnectedThread? = null
/**
* Return the current connection state.
*/
@get:Synchronized
var mState: Int
private set
private var mNewState: Int
/**
* Update UI title according to the current state of the chat connection
*/
@Synchronized
private fun updateUserInterfaceTitle() {
mState = mState
// Log.d(TAG, "updateUserInterfaceTitle() " + mNewState + " -> " + state)
mNewState = mState
// Give the new state to the Handler so the UI Activity can update
mHandler.obtainMessage(Constants.MESSAGE_STATE_CHANGE, mNewState, -1).sendToTarget()
}
/**
* Start the chat service. Specifically start AcceptThread to begin a
* session in listening (server) mode. Called by the Activity onResume()
*/
@Synchronized
fun start() {
// Log.d(TAG, "start")
// Cancel any thread attempting to make a connection
if (mConnectThread != null) {
mConnectThread!!.cancel()
mConnectThread = null
}
// Cancel any thread currently running a connection
if (mConnectedThread != null) {
mConnectedThread!!.cancel()
mConnectedThread = null
}
// Start the thread to listen on a BluetoothServerSocket
if (mSecureAcceptThread == null) {
mSecureAcceptThread = AcceptThread(true)
mSecureAcceptThread!!.start()
}
if (mInsecureAcceptThread == null) {
mInsecureAcceptThread = AcceptThread(false)
mInsecureAcceptThread!!.start()
}
// Update UI title
updateUserInterfaceTitle()
}
/**
* Start the ConnectThread to initiate a connection to a remote device.
*
* @param device The BluetoothDevice to connect
* @param secure Socket Security type - Secure (true) , Insecure (false)
*/
@Synchronized
fun connect(device: BluetoothDevice, secure: Boolean) {
// Log.d(TAG, "connect to: $device")
// Cancel any thread attempting to make a connection
if (mState == STATE_CONNECTING) {
if (mConnectThread != null) {
mConnectThread!!.cancel()
mConnectThread = null
}
}
// Cancel any thread currently running a connection
if (mConnectedThread != null) {
mConnectedThread!!.cancel()
mConnectedThread = null
}
// Start the thread to connect with the given device
mConnectThread = ConnectThread(device, secure)
mConnectThread!!.start()
// Update UI title
updateUserInterfaceTitle()
}
/**
* Start the ConnectedThread to begin managing a Bluetooth connection
*
* @param socket The BluetoothSocket on which the connection was made
* @param device The BluetoothDevice that has been connected
*/
@SuppressLint("MissingPermission")
@Synchronized
fun connected(socket: BluetoothSocket?, device: BluetoothDevice, socketType: String) {
// Log.d(TAG, "connected, Socket Type:$socketType")
// Cancel the thread that completed the connection
if (mConnectThread != null) {
mConnectThread!!.cancel()
mConnectThread = null
}
// Cancel any thread currently running a connection
if (mConnectedThread != null) {
mConnectedThread!!.cancel()
mConnectedThread = null
}
// Cancel the accept thread because we only want to connect to one device
if (mSecureAcceptThread != null) {
mSecureAcceptThread!!.cancel()
mSecureAcceptThread = null
}
if (mInsecureAcceptThread != null) {
mInsecureAcceptThread!!.cancel()
mInsecureAcceptThread = null
}
// Start the thread to manage the connection and perform transmissions
mConnectedThread = ConnectedThread(socket, socketType)
mConnectedThread!!.start()
// Send the name of the connected device back to the UI Activity
val msg = mHandler.obtainMessage(Constants.MESSAGE_DEVICE_NAME)
val bundle = Bundle()
bundle.putString(Constants.DEVICE_NAME, device.name)
msg.data = bundle
mHandler.sendMessage(msg)
// Update UI title
updateUserInterfaceTitle()
}
/**
* Stop all threads
*/
@Synchronized
fun stop() {
// Log.d(TAG, "stop")
if (mConnectThread != null) {
mConnectThread!!.cancel()
mConnectThread = null
}
if (mConnectedThread != null) {
mConnectedThread!!.cancel()
mConnectedThread = null
}
if (mSecureAcceptThread != null) {
mSecureAcceptThread!!.cancel()
mSecureAcceptThread = null
}
if (mInsecureAcceptThread != null) {
mInsecureAcceptThread!!.cancel()
mInsecureAcceptThread = null
}
mState = STATE_NONE
// Update UI title
updateUserInterfaceTitle()
}
/**
* Write to the ConnectedThread in an unsynchronized manner
*
* @param out The bytes to write
* @see ConnectedThread.write
*/
fun write(out: ByteArray?) {
// Create temporary object
var r: ConnectedThread?
// Synchronize a copy of the ConnectedThread
synchronized(this) {
if (mState != STATE_CONNECTED) return
r = mConnectedThread
}
// Perform the write unsynchronized
r!!.write(out)
}
/**
* Indicate that the connection attempt failed and notify the UI Activity.
*/
private fun connectionFailed() {
// Send a failure message back to the Activity
val msg = mHandler.obtainMessage(Constants.MESSAGE_TOAST)
val bundle = Bundle()
bundle.putString(Constants.TOAST, "Unable to connect device")
msg.data = bundle
mHandler.sendMessage(msg)
mState = STATE_NONE
// Update UI title
updateUserInterfaceTitle()
// Start the service over to restart listening mode
start()
}
/**
* Indicate that the connection was lost and notify the UI Activity.
*/
private fun connectionLost() {
// Send a failure message back to the Activity
val msg = mHandler.obtainMessage(Constants.MESSAGE_TOAST)
val bundle = Bundle()
bundle.putString(Constants.TOAST, "Device connection was lost")
msg.data = bundle
mHandler.sendMessage(msg)
mState = STATE_NONE
// Update UI title
updateUserInterfaceTitle()
// Start the service over to restart listening mode
start()
}
/**
* This thread runs while listening for incoming connections. It behaves
* like a server-side client. It runs until a connection is accepted
* (or until cancelled).
*/
@SuppressLint("MissingPermission")
private inner class AcceptThread(secure: Boolean) : Thread() {
// The local server socket
private val mmServerSocket: BluetoothServerSocket?
private val mSocketType: String
override fun run() {
// Log.d(
// TAG, "Socket Type: " + mSocketType +
// "BEGIN mAcceptThread" + this
// )
name = "AcceptThread$mSocketType"
var socket: BluetoothSocket?
// Listen to the server socket if we're not connected
while (mState != STATE_CONNECTED) {
socket = try {
// This is a blocking call and will only return on a
// successful connection or an exception
mmServerSocket!!.accept()
} catch (e: IOException) {
// Log.e(TAG, "Socket Type: " + mSocketType + "accept() failed", e)
break
}
// If a connection was accepted
if (socket != null) {
synchronized(this@BluetoothChatService) {
when (mState) {
STATE_LISTEN, STATE_CONNECTING -> // Situation normal. Start the connected thread.
connected(
socket, socket.remoteDevice,
mSocketType
)
STATE_NONE, STATE_CONNECTED -> // Either not ready or already connected. Terminate new socket.
try {
socket.close()
} catch (e: IOException) {
// Log.e(
// TAG,
// "Could not close unwanted socket",
// e
// )
}
}
}
}
}
// Log.i(
// TAG,
// "END mAcceptThread, socket Type: $mSocketType"
// )
}
fun cancel() {
// Log.d(TAG, "Socket Type" + mSocketType + "cancel " + this)
try {
mmServerSocket!!.close()
} catch (e: IOException) {
// Log.e(TAG, "Socket Type" + mSocketType + "close() of server failed", e)
}
}
init {
var tmp: BluetoothServerSocket? = null
mSocketType = if (secure) "Secure" else "Insecure"
// Create a new listening server socket
try {
tmp = if (secure) {
mAdapter.listenUsingRfcommWithServiceRecord(
NAME_SECURE,
MY_UUID_SECURE
)
} else {
mAdapter.listenUsingInsecureRfcommWithServiceRecord(
NAME_INSECURE, MY_UUID_INSECURE
)
}
} catch (e: IOException) {
// Log.e(TAG, "Socket Type: " + mSocketType + "listen() failed", e)
}
mmServerSocket = tmp
mState = STATE_LISTEN
}
}
/**
* This thread runs while attempting to make an outgoing connection
* with a device. It runs straight through; the connection either
* succeeds or fails.
*/
@SuppressLint("MissingPermission")
private inner class ConnectThread(private val mmDevice: BluetoothDevice, secure: Boolean) :
Thread() {
private val mmSocket: BluetoothSocket?
private val mSocketType: String
override fun run() {
// Log.i(
// TAG,
// "BEGIN mConnectThread SocketType:$mSocketType"
// )
name = "ConnectThread$mSocketType"
// Always cancel discovery because it will slow down a connection
mAdapter.cancelDiscovery()
// Make a connection to the BluetoothSocket
try {
// This is a blocking call and will only return on a
// successful connection or an exception
mmSocket!!.connect()
} catch (e: IOException) {
// Close the socket
try {
mmSocket!!.close()
} catch (e2: IOException) {
// Log.e(
// TAG, "unable to close() " + mSocketType +
// " socket during connection failure", e2
// )
}
connectionFailed()
return
}
// Reset the ConnectThread because we're done
synchronized(this@BluetoothChatService) { mConnectThread = null }
// Start the connected thread
connected(mmSocket, mmDevice, mSocketType)
}
fun cancel() {
try {
mmSocket!!.close()
} catch (e: IOException) {
// Log.e(
// TAG,
// "close() of connect $mSocketType socket failed", e
// )
}
}
init {
var tmp: BluetoothSocket? = null
mSocketType = if (secure) "Secure" else "Insecure"
// Get a BluetoothSocket for a connection with the
// given BluetoothDevice
try {
tmp = if (secure) {
// if (ActivityCompat.checkSelfPermission(
// this,
// Manifest.permission.BLUETOOTH_CONNECT
// ) != PackageManager.PERMISSION_GRANTED
// ) {
// // TODO: Consider calling
// // ActivityCompat#requestPermissions
// // here to request the missing permissions, and then overriding
// // public void onRequestPermissionsResult(int requestCode, String[] permissions,
// // int[] grantResults)
// // to handle the case where the user grants the permission. See the documentation
// // for ActivityCompat#requestPermissions for more details.
// return
// }
mmDevice.createRfcommSocketToServiceRecord(
MY_UUID_SECURE
)
} else {
mmDevice.createInsecureRfcommSocketToServiceRecord(
MY_UUID_INSECURE
)
}
} catch (e: IOException) {
// Log.e(TAG, "Socket Type: " + mSocketType + "create() failed", e)
}
mmSocket = tmp
mState = STATE_CONNECTING
}
}
/**
* This thread runs during a connection with a remote device.
* It handles all incoming and outgoing transmissions.
*/
private inner class ConnectedThread(socket: BluetoothSocket?, socketType: String) :
Thread() {
private val mmSocket: BluetoothSocket?
private val mmInStream: InputStream?
private val mmOutStream: OutputStream?
override fun run() {
// Log.i(TAG, "BEGIN mConnectedThread")
val buffer = ByteArray(1024)
var bytes: Int
// Keep listening to the InputStream while connected
while (mState == STATE_CONNECTED) {
try {
// Read from the InputStream
bytes = mmInStream!!.read(buffer)
// Send the obtained bytes to the UI Activity
mHandler.obtainMessage(Constants.MESSAGE_READ, bytes, -1, buffer)
.sendToTarget()
} catch (e: IOException) {
// Log.e(TAG, "disconnected", e)
connectionLost()
break
}
}
}
/**
* Write to the connected OutStream.
*
* @param buffer The bytes to write
*/
fun write(buffer: ByteArray?) {
try {
mmOutStream!!.write(buffer)
// Share the sent message back to the UI Activity
mHandler.obtainMessage(Constants.MESSAGE_WRITE, -1, -1, buffer)
.sendToTarget()
} catch (e: IOException) {
// Log.e(TAG, "Exception during write", e)
}
}
fun cancel() {
try {
mmSocket!!.close()
} catch (e: IOException) {
// Log.e(TAG, "close() of connect socket failed", e)
}
}
init {
// Log.d(TAG, "create ConnectedThread: $socketType")
mmSocket = socket
var tmpIn: InputStream? = null
var tmpOut: OutputStream? = null
// Get the BluetoothSocket input and output streams
try {
tmpIn = socket!!.inputStream
tmpOut = socket.outputStream
} catch (e: IOException) {
// Log.e(TAG, "temp sockets not created", e)
}
mmInStream = tmpIn
mmOutStream = tmpOut
mState = STATE_CONNECTED
}
}
companion object {
// Debugging
private const val TAG = "BluetoothChatService"
// Name for the SDP record when creating server socket
private const val NAME_SECURE = "BluetoothChatSecure"
private const val NAME_INSECURE = "BluetoothChatInsecure"
// Unique UUID for this application
private val MY_UUID_SECURE = UUID.fromString("fa87c0d0-afac-11de-8a39-0800200c9a66")
private val MY_UUID_INSECURE = UUID.fromString("8ce255c0-200a-11e0-ac64-0800200c9a66")
// Constants that indicate the current connection state
const val STATE_NONE = 0 // we're doing nothing
const val STATE_LISTEN = 1 // now listening for incoming connections
const val STATE_CONNECTING = 2 // now initiating an outgoing connection
const val STATE_CONNECTED = 3 // now connected to a remote device
}
/**
* Constructor. Prepares a new BluetoothChat session.
*
* @param context The UI Activity Context
* @param handler A Handler to send messages back to the UI Activity
*/
init {
mAdapter = BluetoothAdapter.getDefaultAdapter()
mState = STATE_NONE
mNewState = mState
mHandler = handler
}
}

@ -0,0 +1,19 @@
package com.rookiedev.hexapod.network
/**
* Defines several constants used between [BluetoothChatService] and the UI.
*/
interface Constants {
companion object {
// Message types sent from the BluetoothChatService Handler
const val MESSAGE_STATE_CHANGE = 1
const val MESSAGE_READ = 2
const val MESSAGE_WRITE = 3
const val MESSAGE_DEVICE_NAME = 4
const val MESSAGE_TOAST = 5
// Key names received from the BluetoothChatService Handler
const val DEVICE_NAME = "device_name"
const val TOAST = "toast"
}
}
Loading…
Cancel
Save