Remove device from ContactsFragment when disconnected.
Also changed some variable/class/function names.
This commit is contained in:
parent
2129107684
commit
6b2ef30888
4 changed files with 49 additions and 42 deletions
|
@ -8,7 +8,7 @@ import android.content.{BroadcastReceiver, Context, Intent, IntentFilter}
|
||||||
import android.os.Handler
|
import android.os.Handler
|
||||||
import android.util.Log
|
import android.util.Log
|
||||||
import com.nutomic.ensichat.R
|
import com.nutomic.ensichat.R
|
||||||
import com.nutomic.ensichat.bluetooth.ChatService.{OnDeviceConnectedListener, OnMessageReceivedListener}
|
import com.nutomic.ensichat.bluetooth.ChatService.{OnConnectionChangedListener, OnMessageReceivedListener}
|
||||||
import com.nutomic.ensichat.messages._
|
import com.nutomic.ensichat.messages._
|
||||||
|
|
||||||
import scala.collection.immutable.{HashMap, HashSet, TreeSet}
|
import scala.collection.immutable.{HashMap, HashSet, TreeSet}
|
||||||
|
@ -24,8 +24,8 @@ object ChatService {
|
||||||
|
|
||||||
val KEY_GENERATION_FINISHED = "com.nutomic.ensichat.messages.KEY_GENERATION_FINISHED"
|
val KEY_GENERATION_FINISHED = "com.nutomic.ensichat.messages.KEY_GENERATION_FINISHED"
|
||||||
|
|
||||||
trait OnDeviceConnectedListener {
|
trait OnConnectionChangedListener {
|
||||||
def onDeviceConnected(devices: Map[Device.ID, Device]): Unit
|
def onConnectionChanged(devices: Map[Device.ID, Device]): Unit
|
||||||
}
|
}
|
||||||
|
|
||||||
trait OnMessageReceivedListener {
|
trait OnMessageReceivedListener {
|
||||||
|
@ -52,7 +52,7 @@ class ChatService extends Service {
|
||||||
* but on a Nexus S (Android 4.1.2), these functions are garbage collected even when
|
* but on a Nexus S (Android 4.1.2), these functions are garbage collected even when
|
||||||
* referenced.
|
* referenced.
|
||||||
*/
|
*/
|
||||||
private var deviceListeners = new HashSet[WeakReference[OnDeviceConnectedListener]]()
|
private var connectionListeners = new HashSet[WeakReference[OnConnectionChangedListener]]()
|
||||||
|
|
||||||
private val messageListeners =
|
private val messageListeners =
|
||||||
mutable.HashMap[Device.ID, mutable.Set[WeakReference[OnMessageReceivedListener]]]()
|
mutable.HashMap[Device.ID, mutable.Set[WeakReference[OnMessageReceivedListener]]]()
|
||||||
|
@ -137,7 +137,7 @@ class ChatService extends Service {
|
||||||
val device: Device =
|
val device: Device =
|
||||||
new Device(intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE), false)
|
new Device(intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE), false)
|
||||||
devices += (device.id -> device)
|
devices += (device.id -> device)
|
||||||
new ConnectThread(device, onConnected).start()
|
new ConnectThread(device, onConnectionChanged).start()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -168,34 +168,40 @@ class ChatService extends Service {
|
||||||
private def startBluetoothConnections(): Unit = {
|
private def startBluetoothConnections(): Unit = {
|
||||||
cancelDiscovery = false
|
cancelDiscovery = false
|
||||||
discover()
|
discover()
|
||||||
ListenThread = new ListenThread(getString(R.string.app_name), bluetoothAdapter, onConnected)
|
ListenThread =
|
||||||
|
new ListenThread(getString(R.string.app_name), bluetoothAdapter, onConnectionChanged)
|
||||||
ListenThread.start()
|
ListenThread.start()
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Registers a listener that is called whenever a new device is connected.
|
* Registers a listener that is called whenever a new device is connected.
|
||||||
*/
|
*/
|
||||||
def registerDeviceListener(listener: OnDeviceConnectedListener): Unit = {
|
def registerConnectionListener(listener: OnConnectionChangedListener): Unit = {
|
||||||
deviceListeners += new WeakReference[OnDeviceConnectedListener](listener)
|
connectionListeners += new WeakReference[OnConnectionChangedListener](listener)
|
||||||
listener.onDeviceConnected(devices)
|
listener.onConnectionChanged(devices)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Called when a Bluetooth device is connected.
|
* Called when a Bluetooth device is connected.
|
||||||
*
|
*
|
||||||
* Adds the device to [[connections]], notifies all [[deviceListeners]], sends DeviceInfoMessage.
|
* Adds the device to [[connections]], notifies all [[connectionListeners]], sends DeviceInfoMessage.
|
||||||
|
*
|
||||||
|
* @param device The updated device info for the remote device.
|
||||||
|
* @param socket A socket for data transfer if device.connected is true, otherwise null.
|
||||||
*/
|
*/
|
||||||
def onConnected(device: Device, socket: BluetoothSocket): Unit = {
|
def onConnectionChanged(device: Device, socket: BluetoothSocket): Unit = {
|
||||||
val updatedDevice: Device = new Device(device.bluetoothDevice, true)
|
devices += (device.id -> device)
|
||||||
devices += (device.id -> updatedDevice)
|
|
||||||
connections += (device.id ->
|
|
||||||
new TransferThread(updatedDevice, socket, localDeviceId, Encrypt, handleNewMessage))
|
|
||||||
connections(device.id).start()
|
|
||||||
|
|
||||||
send(new DeviceInfoMessage(localDeviceId, device.id, new Date(), Encrypt.getLocalPublicKey))
|
if (device.connected) {
|
||||||
deviceListeners.foreach(l => l.get match {
|
connections += (device.id ->
|
||||||
case Some(_) => l.apply().onDeviceConnected(devices)
|
new TransferThread(device, socket, this, Encrypt, handleNewMessage))
|
||||||
case None => deviceListeners -= l
|
connections(device.id).start()
|
||||||
|
send(new DeviceInfoMessage(localDeviceId, device.id, new Date(), Encrypt.getLocalPublicKey))
|
||||||
|
}
|
||||||
|
|
||||||
|
connectionListeners.foreach(l => l.get match {
|
||||||
|
case Some(_) => l.apply().onConnectionChanged(devices)
|
||||||
|
case None => connectionListeners -= l
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -33,7 +33,7 @@ class ConnectThread(device: Device, onConnected: (Device, BluetoothSocket) => Un
|
||||||
}
|
}
|
||||||
|
|
||||||
Log.i(Tag, "Successfully connected to device " + device.name)
|
Log.i(Tag, "Successfully connected to device " + device.name)
|
||||||
onConnected(device, Socket)
|
onConnected(new Device(device.bluetoothDevice, true), Socket)
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -13,11 +13,11 @@ import com.nutomic.ensichat.messages.{Crypto, DeviceInfoMessage, Message, TextMe
|
||||||
*
|
*
|
||||||
* @param device The bluetooth device to interact with.
|
* @param device The bluetooth device to interact with.
|
||||||
* @param socket An open socket to the given device.
|
* @param socket An open socket to the given device.
|
||||||
* @param encrypt Object used to handle signing and encryption of messages.
|
* @param crypto Object used to handle signing and encryption of messages.
|
||||||
* @param onReceive Called when a message was received from the other device.
|
* @param onReceive Called when a message was received from the other device.
|
||||||
*/
|
*/
|
||||||
class TransferThread(device: Device, socket: BluetoothSocket, localDevice: Device.ID,
|
class TransferThread(device: Device, socket: BluetoothSocket, service: ChatService,
|
||||||
encrypt: Crypto, onReceive: (Message) => Unit) extends Thread {
|
crypto: Crypto, onReceive: (Message) => Unit) extends Thread {
|
||||||
|
|
||||||
private val Tag: String = "TransferThread"
|
private val Tag: String = "TransferThread"
|
||||||
|
|
||||||
|
@ -44,58 +44,59 @@ class TransferThread(device: Device, socket: BluetoothSocket, localDevice: Devic
|
||||||
override def run(): Unit = {
|
override def run(): Unit = {
|
||||||
Log.i(Tag, "Starting data transfer with " + device.toString)
|
Log.i(Tag, "Starting data transfer with " + device.toString)
|
||||||
|
|
||||||
// Keep listening to the InputStream while connected
|
while (socket.isConnected) {
|
||||||
while (true) {
|
|
||||||
try {
|
try {
|
||||||
val bytes = new Array[Byte](MaxMessageLength)
|
val bytes = new Array[Byte](MaxMessageLength)
|
||||||
InStream.read(bytes)
|
InStream.read(bytes)
|
||||||
val (msg, signature) = Message.read(bytes)
|
val (message, signature) = Message.read(bytes)
|
||||||
var messageValid = true
|
var messageValid = true
|
||||||
|
|
||||||
if (msg.sender != device.id) {
|
if (message.sender != device.id) {
|
||||||
Log.i(Tag, "Dropping message with invalid sender from " + device.id)
|
Log.i(Tag, "Dropping message with invalid sender from " + device.id)
|
||||||
messageValid = false
|
messageValid = false
|
||||||
}
|
}
|
||||||
|
|
||||||
if (msg.receiver != localDevice) {
|
if (message.receiver != service.localDeviceId) {
|
||||||
Log.i(Tag, "Dropping message with different receiver from " + device.id)
|
Log.i(Tag, "Dropping message with different receiver from " + device.id)
|
||||||
messageValid = false
|
messageValid = false
|
||||||
}
|
}
|
||||||
|
|
||||||
// Add public key for new, directly connected device.
|
// Add public key for new, directly connected device.
|
||||||
// Explicitly check that message was not forwarded or spoofed.
|
// Explicitly check that message was not forwarded or spoofed.
|
||||||
if (msg.isInstanceOf[DeviceInfoMessage] && !encrypt.havePublicKey(msg.sender) &&
|
if (message.isInstanceOf[DeviceInfoMessage] && !crypto.havePublicKey(message.sender) &&
|
||||||
msg.sender == device.id) {
|
message.sender == device.id) {
|
||||||
val dim = msg.asInstanceOf[DeviceInfoMessage]
|
val dim = message.asInstanceOf[DeviceInfoMessage]
|
||||||
// Permanently store public key for new local devices (also check signature).
|
// Permanently store public key for new local devices (also check signature).
|
||||||
if (encrypt.isValidSignature(msg, signature, dim.publicKey)) {
|
if (crypto.isValidSignature(message, signature, dim.publicKey)) {
|
||||||
encrypt.addPublicKey(device.id, dim.publicKey)
|
crypto.addPublicKey(device.id, dim.publicKey)
|
||||||
Log.i(Tag, "Added public key for new device " + device.name)
|
Log.i(Tag, "Added public key for new device " + device.name)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!encrypt.isValidSignature(msg, signature)) {
|
if (!crypto.isValidSignature(message, signature)) {
|
||||||
Log.i(Tag, "Dropping message with invalid signature from " + device.id)
|
Log.i(Tag, "Dropping message with invalid signature from " + device.id)
|
||||||
messageValid = false
|
messageValid = false
|
||||||
}
|
}
|
||||||
|
|
||||||
if (messageValid) {
|
if (messageValid) {
|
||||||
msg match {
|
message match {
|
||||||
case m: TextMessage => onReceive(m)
|
case m: TextMessage => onReceive(m)
|
||||||
case m: DeviceInfoMessage => encrypt.addPublicKey(msg.sender, m.publicKey)
|
case m: DeviceInfoMessage => crypto.addPublicKey(message.sender, m.publicKey)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch {
|
} catch {
|
||||||
case e: IOException =>
|
case e: IOException =>
|
||||||
Log.e(Tag, "Disconnected from device", e)
|
Log.w(Tag, "Connection to " + device.name + " closed with exception", e)
|
||||||
|
service.onConnectionChanged(new Device(device.bluetoothDevice, false), null)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
service.onConnectionChanged(new Device(device.bluetoothDevice, false), null)
|
||||||
}
|
}
|
||||||
|
|
||||||
def send(message: Message): Unit = {
|
def send(message: Message): Unit = {
|
||||||
try {
|
try {
|
||||||
val sig = encrypt.calculateSignature(message)
|
val sig = crypto.calculateSignature(message)
|
||||||
val bytes = message.write(sig)
|
val bytes = message.write(sig)
|
||||||
OutStream.write(bytes)
|
OutStream.write(bytes)
|
||||||
} catch {
|
} catch {
|
||||||
|
|
|
@ -13,7 +13,7 @@ import com.nutomic.ensichat.util.DevicesAdapter
|
||||||
/**
|
/**
|
||||||
* Lists all nearby, connected devices.
|
* Lists all nearby, connected devices.
|
||||||
*/
|
*/
|
||||||
class ContactsFragment extends ListFragment with ChatService.OnDeviceConnectedListener {
|
class ContactsFragment extends ListFragment with ChatService.OnConnectionChangedListener {
|
||||||
|
|
||||||
private var chatService: ChatService = _
|
private var chatService: ChatService = _
|
||||||
|
|
||||||
|
@ -21,7 +21,7 @@ class ContactsFragment extends ListFragment with ChatService.OnDeviceConnectedLi
|
||||||
override def onServiceConnected(componentName: ComponentName, iBinder: IBinder): Unit = {
|
override def onServiceConnected(componentName: ComponentName, iBinder: IBinder): Unit = {
|
||||||
val binder: ChatServiceBinder = iBinder.asInstanceOf[ChatServiceBinder]
|
val binder: ChatServiceBinder = iBinder.asInstanceOf[ChatServiceBinder]
|
||||||
chatService = binder.getService
|
chatService = binder.getService
|
||||||
chatService.registerDeviceListener(ContactsFragment.this)
|
chatService.registerConnectionListener(ContactsFragment.this)
|
||||||
}
|
}
|
||||||
|
|
||||||
override def onServiceDisconnected(componentName: ComponentName): Unit = {
|
override def onServiceDisconnected(componentName: ComponentName): Unit = {
|
||||||
|
@ -71,7 +71,7 @@ class ContactsFragment extends ListFragment with ChatService.OnDeviceConnectedLi
|
||||||
/**
|
/**
|
||||||
* Displays newly connected devices in the list.
|
* Displays newly connected devices in the list.
|
||||||
*/
|
*/
|
||||||
override def onDeviceConnected(devices: Map[Device.ID, Device]): Unit = {
|
override def onConnectionChanged(devices: Map[Device.ID, Device]): Unit = {
|
||||||
if (getActivity == null)
|
if (getActivity == null)
|
||||||
return
|
return
|
||||||
|
|
||||||
|
|
Reference in a new issue