Remove device from ContactsFragment when disconnected.

Also changed some variable/class/function names.
This commit is contained in:
Felix Ableitner 2014-11-07 18:25:53 +02:00
parent 2129107684
commit 6b2ef30888
4 changed files with 49 additions and 42 deletions

View file

@ -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
}) })
} }

View file

@ -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)
} }
} }

View file

@ -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 {

View file

@ -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