Use notification and seperate activity to add contact.
This might cause problems if the activity is closed after choosing yes, and before the other user confirms. We should probably store the information in the service.
This commit is contained in:
parent
04fd815001
commit
3a90f2d9a3
6 changed files with 196 additions and 151 deletions
|
@ -34,6 +34,11 @@
|
||||||
android:value=".activities.MainActivity" />
|
android:value=".activities.MainActivity" />
|
||||||
</activity>
|
</activity>
|
||||||
|
|
||||||
|
<activity
|
||||||
|
android:name=".activities.ConfirmAddContactDialog"
|
||||||
|
android:theme="@android:style/Theme.Translucent.NoTitleBar"
|
||||||
|
android:excludeFromRecents="true" />
|
||||||
|
|
||||||
<activity
|
<activity
|
||||||
android:name=".activities.SettingsActivity"
|
android:name=".activities.SettingsActivity"
|
||||||
android:label="@string/settings" >
|
android:label="@string/settings" >
|
||||||
|
|
|
@ -27,6 +27,12 @@
|
||||||
<!-- Activity title -->
|
<!-- Activity title -->
|
||||||
<string name="add_contacts">Add Contacts</string>
|
<string name="add_contacts">Add Contacts</string>
|
||||||
|
|
||||||
|
<!-- Empty text for devices list -->
|
||||||
|
<string name="no_devices_nearby">No nearby devices found</string>
|
||||||
|
|
||||||
|
|
||||||
|
<!-- ConfirmAddContactDialog -->
|
||||||
|
|
||||||
<!-- Title of dialog shown when clicking a device -->
|
<!-- Title of dialog shown when clicking a device -->
|
||||||
<string name="add_contact_dialog">Do you want to add %1$s as a new contact?</string>
|
<string name="add_contact_dialog">Do you want to add %1$s as a new contact?</string>
|
||||||
|
|
||||||
|
@ -39,15 +45,12 @@
|
||||||
<!-- Information text shown in the "add contact" dialog -->
|
<!-- Information text shown in the "add contact" dialog -->
|
||||||
<string name="add_contact_dialog_hint">Before accepting, make sure the images match on both devices</string>
|
<string name="add_contact_dialog_hint">Before accepting, make sure the images match on both devices</string>
|
||||||
|
|
||||||
<!-- Empty text for devices list -->
|
<!-- Toast shown when a contact is not added because it was not accepted on the other device -->
|
||||||
<string name="no_devices_nearby">No nearby devices found</string>
|
<string name="contact_not_added">Contact not added (denied by other device)</string>
|
||||||
|
|
||||||
<!-- Toast shown when a new contact was added. Parameter is contact name -->
|
<!-- Toast shown when a new contact was added. Parameter is contact name -->
|
||||||
<string name="contact_added">%1$s was added as a contact</string>
|
<string name="contact_added">%1$s was added as a contact</string>
|
||||||
|
|
||||||
<!-- Toast shown when a contact is not added because it was not accepted on the other device -->
|
|
||||||
<string name="contact_not_added">Contact not added (denied by other device)</string>
|
|
||||||
|
|
||||||
|
|
||||||
<!-- SettingsActivity -->
|
<!-- SettingsActivity -->
|
||||||
|
|
||||||
|
@ -60,4 +63,10 @@
|
||||||
<!-- Preference title -->
|
<!-- Preference title -->
|
||||||
<string name="scan_interval_seconds">Scan Interval (seconds)</string>
|
<string name="scan_interval_seconds">Scan Interval (seconds)</string>
|
||||||
|
|
||||||
|
|
||||||
|
<!-- ChatService -->
|
||||||
|
|
||||||
|
<!-- Notification text for incoming friend request -->
|
||||||
|
<string name="notification_friend_request">New friend request!</string>
|
||||||
|
|
||||||
</resources>
|
</resources>
|
||||||
|
|
|
@ -1,51 +1,26 @@
|
||||||
package com.nutomic.ensichat.activities
|
package com.nutomic.ensichat.activities
|
||||||
|
|
||||||
import android.app.AlertDialog
|
import android.content.Intent
|
||||||
import android.content.DialogInterface.OnClickListener
|
|
||||||
import android.content.{Intent, Context, DialogInterface}
|
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import android.support.v4.app.NavUtils
|
import android.support.v4.app.NavUtils
|
||||||
import android.util.Log
|
|
||||||
import android.view._
|
import android.view._
|
||||||
import android.widget.AdapterView.OnItemClickListener
|
import android.widget.AdapterView.OnItemClickListener
|
||||||
import android.widget._
|
import android.widget._
|
||||||
import com.nutomic.ensichat.R
|
import com.nutomic.ensichat.R
|
||||||
import com.nutomic.ensichat.protocol.messages.{Message, RequestAddContact, ResultAddContact}
|
import com.nutomic.ensichat.protocol.ChatService
|
||||||
import com.nutomic.ensichat.protocol.{User, Address, ChatService, Crypto}
|
import com.nutomic.ensichat.protocol.messages.RequestAddContact
|
||||||
import com.nutomic.ensichat.util.{Database, UsersAdapter, IdenticonGenerator}
|
import com.nutomic.ensichat.util.UsersAdapter
|
||||||
|
|
||||||
import scala.collection.SortedSet
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Lists all nearby, connected devices and allows adding them to contacts.
|
* Lists all nearby, connected devices and allows adding them to be added as contacts.
|
||||||
*
|
|
||||||
* Adding a contact requires confirmation on both sides.
|
|
||||||
*/
|
*/
|
||||||
class AddContactsActivity extends EnsiChatActivity with ChatService.OnConnectionsChangedListener
|
class AddContactsActivity extends EnsiChatActivity with ChatService.OnConnectionsChangedListener
|
||||||
with OnItemClickListener with ChatService.OnMessageReceivedListener {
|
with OnItemClickListener {
|
||||||
|
|
||||||
private val Tag = "AddContactsActivity"
|
private val Tag = "AddContactsActivity"
|
||||||
|
|
||||||
private lazy val Adapter = new UsersAdapter(this)
|
private lazy val Adapter = new UsersAdapter(this)
|
||||||
|
|
||||||
private lazy val Database = service.Database
|
|
||||||
|
|
||||||
private lazy val Crypto = new Crypto(this)
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Map of devices that should be added.
|
|
||||||
*/
|
|
||||||
private var currentlyAdding = Map[User, AddContactInfo]()
|
|
||||||
.withDefaultValue(new AddContactInfo(false, false))
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Holds confirmation status for adding contacts.
|
|
||||||
*
|
|
||||||
* @param localConfirmed If true, the local user has accepted adding the contact.
|
|
||||||
* @param remoteConfirmed If true, the remote contact has accepted adding this device as contact.
|
|
||||||
*/
|
|
||||||
private case class AddContactInfo(localConfirmed: Boolean, remoteConfirmed: Boolean)
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Initializes layout, registers connection and message listeners.
|
* Initializes layout, registers connection and message listeners.
|
||||||
*/
|
*/
|
||||||
|
@ -61,8 +36,7 @@ class AddContactsActivity extends EnsiChatActivity with ChatService.OnConnection
|
||||||
|
|
||||||
runOnServiceConnected(() => {
|
runOnServiceConnected(() => {
|
||||||
service.registerConnectionListener(AddContactsActivity.this)
|
service.registerConnectionListener(AddContactsActivity.this)
|
||||||
service.registerMessageListener(this)
|
service.Database.runOnContactsUpdated(updateList)
|
||||||
Database.runOnContactsUpdated(updateList)
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -74,95 +48,9 @@ class AddContactsActivity extends EnsiChatActivity with ChatService.OnConnection
|
||||||
override def onItemClick(parent: AdapterView[_], view: View, position: Int, id: Long): Unit = {
|
override def onItemClick(parent: AdapterView[_], view: View, position: Int, id: Long): Unit = {
|
||||||
val contact = Adapter.getItem(position)
|
val contact = Adapter.getItem(position)
|
||||||
service.sendTo(contact.Address, new RequestAddContact())
|
service.sendTo(contact.Address, new RequestAddContact())
|
||||||
addDeviceDialog(contact)
|
val intent = new Intent(this, classOf[ConfirmAddContactDialog])
|
||||||
}
|
intent.putExtra(ConfirmAddContactDialog.ExtraContactAddress, contact.Address.toString)
|
||||||
|
|
||||||
/**
|
|
||||||
* Shows a dialog to accept/deny adding a device as a new contact.
|
|
||||||
*/
|
|
||||||
private def addDeviceDialog(contact: User): Unit = {
|
|
||||||
// Listener for dialog button clicks.
|
|
||||||
val onClick = new OnClickListener {
|
|
||||||
override def onClick(dialogInterface: DialogInterface, i: Int): Unit = i match {
|
|
||||||
case DialogInterface.BUTTON_POSITIVE =>
|
|
||||||
// Local user accepted contact, update state and send info to other device.
|
|
||||||
currentlyAdding +=
|
|
||||||
(contact -> new AddContactInfo(currentlyAdding(contact).localConfirmed, true))
|
|
||||||
addContactIfBothConfirmed(contact)
|
|
||||||
service.sendTo(contact.Address, new ResultAddContact(true))
|
|
||||||
case DialogInterface.BUTTON_NEGATIVE =>
|
|
||||||
// Local user denied adding contact, send info to other device.
|
|
||||||
service.sendTo(contact.Address, new ResultAddContact(false))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
val inflater = getSystemService(Context.LAYOUT_INFLATER_SERVICE).asInstanceOf[LayoutInflater]
|
|
||||||
val view = inflater.inflate(R.layout.dialog_add_contact, null)
|
|
||||||
|
|
||||||
val local = view.findViewById(R.id.local_identicon).asInstanceOf[ImageView]
|
|
||||||
local.setImageBitmap(
|
|
||||||
IdenticonGenerator.generate(Crypto.getLocalAddress, (150, 150), this))
|
|
||||||
val remoteTitle = view.findViewById(R.id.remote_identicon_title).asInstanceOf[TextView]
|
|
||||||
remoteTitle.setText(getString(R.string.remote_fingerprint_title, contact.Name))
|
|
||||||
val remote = view.findViewById(R.id.remote_identicon).asInstanceOf[ImageView]
|
|
||||||
remote.setImageBitmap(IdenticonGenerator.generate(contact.Address, (150, 150), this))
|
|
||||||
|
|
||||||
new AlertDialog.Builder(this)
|
|
||||||
.setTitle(getString(R.string.add_contact_dialog, contact.Name))
|
|
||||||
.setView(view)
|
|
||||||
.setPositiveButton(android.R.string.yes, onClick)
|
|
||||||
.setNegativeButton(android.R.string.no, onClick)
|
|
||||||
.show()
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Handles incoming [[RequestAddContact]] and [[ResultAddContact]] messages.
|
|
||||||
*
|
|
||||||
* These are only handled here and require user action, so contacts can only be added if
|
|
||||||
* the user is in this activity.
|
|
||||||
*/
|
|
||||||
override def onMessageReceived(msg: Message): Unit = {
|
|
||||||
if (msg.Header.Target != Crypto.getLocalAddress)
|
|
||||||
return
|
|
||||||
|
|
||||||
msg.Body match {
|
|
||||||
case _: RequestAddContact =>
|
|
||||||
Log.i(Tag, "Remote device " + msg.Header.Origin + " wants to add us as a contact, showing dialog")
|
|
||||||
service.getConnections.find(_.Address == msg.Header.Origin).foreach(addDeviceDialog)
|
|
||||||
case m: ResultAddContact =>
|
|
||||||
currentlyAdding.keys.find(_.Address == msg.Header.Origin).foreach(contact =>
|
|
||||||
if (m.Accepted) {
|
|
||||||
Log.i(Tag, contact.toString + " accepted us as a contact, updating state")
|
|
||||||
currentlyAdding += (contact ->
|
|
||||||
new AddContactInfo(true, currentlyAdding(contact).remoteConfirmed))
|
|
||||||
addContactIfBothConfirmed(contact)
|
|
||||||
val intent = new Intent(this, classOf[MainActivity])
|
|
||||||
intent.setAction(MainActivity.ActionOpenChat)
|
|
||||||
intent.putExtra(MainActivity.ExtraAddress, msg.Header.Origin.toString)
|
|
||||||
startActivity(intent)
|
startActivity(intent)
|
||||||
finish()
|
|
||||||
} else {
|
|
||||||
Log.i(Tag, contact.toString + " denied us as a contact, showing toast")
|
|
||||||
Toast.makeText(this, R.string.contact_not_added, Toast.LENGTH_LONG).show()
|
|
||||||
currentlyAdding -= contact
|
|
||||||
})
|
|
||||||
case _ =>
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Add the given device to contacts if [[AddContactInfo.localConfirmed]] and
|
|
||||||
* [[AddContactInfo.remoteConfirmed]] are true for it in [[currentlyAdding]].
|
|
||||||
*/
|
|
||||||
private def addContactIfBothConfirmed(contact: User): Unit = {
|
|
||||||
val info = currentlyAdding(contact)
|
|
||||||
if (info.localConfirmed && info.remoteConfirmed) {
|
|
||||||
Log.i(Tag, "Adding new contact " + contact.toString)
|
|
||||||
Database.addContact(contact)
|
|
||||||
Toast.makeText(this, getString(R.string.contact_added, contact.Name), Toast.LENGTH_SHORT)
|
|
||||||
.show()
|
|
||||||
currentlyAdding -= contact
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override def onOptionsItemSelected(item: MenuItem): Boolean = item.getItemId match {
|
override def onOptionsItemSelected(item: MenuItem): Boolean = item.getItemId match {
|
||||||
|
|
|
@ -0,0 +1,121 @@
|
||||||
|
package com.nutomic.ensichat.activities
|
||||||
|
|
||||||
|
import android.app.AlertDialog
|
||||||
|
import android.content.DialogInterface.OnClickListener
|
||||||
|
import android.content.{Context, DialogInterface}
|
||||||
|
import android.os.Bundle
|
||||||
|
import android.util.Log
|
||||||
|
import android.view.{ContextThemeWrapper, LayoutInflater}
|
||||||
|
import android.widget.{ImageView, TextView, Toast}
|
||||||
|
import com.nutomic.ensichat.R
|
||||||
|
import com.nutomic.ensichat.protocol.ChatService.OnMessageReceivedListener
|
||||||
|
import com.nutomic.ensichat.protocol.messages.{Message, RequestAddContact, ResultAddContact}
|
||||||
|
import com.nutomic.ensichat.protocol.{Address, Crypto}
|
||||||
|
import com.nutomic.ensichat.util.IdenticonGenerator
|
||||||
|
|
||||||
|
object ConfirmAddContactDialog {
|
||||||
|
|
||||||
|
val ExtraContactAddress = "contact_address"
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Shows a dialog for adding a new contact (including key fingerprints).
|
||||||
|
*/
|
||||||
|
class ConfirmAddContactDialog extends EnsiChatActivity with OnMessageReceivedListener
|
||||||
|
with OnClickListener {
|
||||||
|
|
||||||
|
private val Tag = "ConfirmAddContactDialog"
|
||||||
|
|
||||||
|
private lazy val User = service.getUser(
|
||||||
|
new Address(getIntent.getStringExtra(ConfirmAddContactDialog.ExtraContactAddress)))
|
||||||
|
|
||||||
|
private var localConfirmed = false
|
||||||
|
|
||||||
|
private var remoteConfirmed = false
|
||||||
|
|
||||||
|
override def onCreate(savedInstanceState: Bundle): Unit = {
|
||||||
|
super.onCreate(savedInstanceState)
|
||||||
|
|
||||||
|
runOnServiceConnected(() => {
|
||||||
|
showDialog()
|
||||||
|
service.registerMessageListener(this)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Shows a dialog to accept/deny adding a device as a new contact.
|
||||||
|
*/
|
||||||
|
private def showDialog(): Unit = {
|
||||||
|
val inflater = getSystemService(Context.LAYOUT_INFLATER_SERVICE).asInstanceOf[LayoutInflater]
|
||||||
|
val view = inflater.inflate(R.layout.dialog_add_contact, null)
|
||||||
|
|
||||||
|
val local = view.findViewById(R.id.local_identicon).asInstanceOf[ImageView]
|
||||||
|
val remote = view.findViewById(R.id.remote_identicon).asInstanceOf[ImageView]
|
||||||
|
val remoteTitle = view.findViewById(R.id.remote_identicon_title).asInstanceOf[TextView]
|
||||||
|
|
||||||
|
local.setImageBitmap(IdenticonGenerator.generate(Crypto.getLocalAddress(this), (150, 150), this))
|
||||||
|
remote.setImageBitmap(IdenticonGenerator.generate(User.Address, (150, 150), this))
|
||||||
|
remoteTitle.setText(getString(R.string.remote_fingerprint_title, User.Name))
|
||||||
|
|
||||||
|
new AlertDialog.Builder(new ContextThemeWrapper(this, R.style.AppTheme))
|
||||||
|
.setTitle(getString(R.string.add_contact_dialog, User.Name))
|
||||||
|
.setView(view)
|
||||||
|
.setCancelable(false)
|
||||||
|
.setPositiveButton(android.R.string.yes, this)
|
||||||
|
.setNegativeButton(android.R.string.no, this)
|
||||||
|
.show()
|
||||||
|
}
|
||||||
|
|
||||||
|
override def onClick(dialogInterface: DialogInterface, i: Int): Unit = {
|
||||||
|
val result = i match {
|
||||||
|
case DialogInterface.BUTTON_POSITIVE =>
|
||||||
|
localConfirmed = true
|
||||||
|
addContactIfBothConfirmed()
|
||||||
|
true
|
||||||
|
case DialogInterface.BUTTON_NEGATIVE =>
|
||||||
|
false
|
||||||
|
}
|
||||||
|
service.sendTo(User.Address, new ResultAddContact(result))
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add the user to contacts if [[localConfirmed]] and [[remoteConfirmed]] are true.
|
||||||
|
*/
|
||||||
|
private def addContactIfBothConfirmed(): Unit = {
|
||||||
|
if (localConfirmed && remoteConfirmed) {
|
||||||
|
Log.i(Tag, "Adding new contact " + User.toString)
|
||||||
|
// Get the user again, in case
|
||||||
|
service.Database.addContact(service.getUser(User.Address))
|
||||||
|
Toast.makeText(this, getString(R.string.contact_added, User.Name), Toast.LENGTH_SHORT)
|
||||||
|
.show()
|
||||||
|
finish()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handles incoming [[RequestAddContact]] and [[ResultAddContact]] messages.
|
||||||
|
*
|
||||||
|
* These are only handled here and require user action, so contacts can only be added if
|
||||||
|
* the user is in this activity.
|
||||||
|
*/
|
||||||
|
override def onMessageReceived(msg: Message): Unit = {
|
||||||
|
if (msg.Header.Origin != User.Address || msg.Header.Target != Crypto.getLocalAddress(this))
|
||||||
|
return
|
||||||
|
|
||||||
|
msg.Body match {
|
||||||
|
case m: ResultAddContact =>
|
||||||
|
if (m.Accepted) {
|
||||||
|
Log.i(Tag, User.toString + " accepted us as a contact, updating state")
|
||||||
|
remoteConfirmed = true
|
||||||
|
addContactIfBothConfirmed()
|
||||||
|
} else {
|
||||||
|
Log.i(Tag, User.toString + " denied us as a contact, showing toast")
|
||||||
|
Toast.makeText(this, R.string.contact_not_added, Toast.LENGTH_LONG).show()
|
||||||
|
finish()
|
||||||
|
}
|
||||||
|
case _ =>
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -1,19 +1,19 @@
|
||||||
package com.nutomic.ensichat.protocol
|
package com.nutomic.ensichat.protocol
|
||||||
|
|
||||||
import android.app.Service
|
import android.app.{Notification, NotificationManager, PendingIntent, Service}
|
||||||
import android.bluetooth.BluetoothAdapter
|
import android.bluetooth.BluetoothAdapter
|
||||||
import android.content.Intent
|
import android.content.{Context, Intent}
|
||||||
import android.os.Handler
|
import android.os.Handler
|
||||||
import android.preference.PreferenceManager
|
import android.preference.PreferenceManager
|
||||||
import android.util.Log
|
import android.util.Log
|
||||||
|
import com.nutomic.ensichat.R
|
||||||
|
import com.nutomic.ensichat.activities.ConfirmAddContactDialog
|
||||||
import com.nutomic.ensichat.bluetooth.BluetoothInterface
|
import com.nutomic.ensichat.bluetooth.BluetoothInterface
|
||||||
import com.nutomic.ensichat.fragments.SettingsFragment
|
import com.nutomic.ensichat.fragments.SettingsFragment
|
||||||
import com.nutomic.ensichat.protocol.ChatService.{OnMessageReceivedListener, OnConnectionsChangedListener}
|
import com.nutomic.ensichat.protocol.ChatService.{OnConnectionsChangedListener, OnMessageReceivedListener}
|
||||||
import com.nutomic.ensichat.protocol.messages._
|
import com.nutomic.ensichat.protocol.messages._
|
||||||
import com.nutomic.ensichat.util.Database
|
import com.nutomic.ensichat.util.Database
|
||||||
|
|
||||||
import scala.collection.SortedSet
|
|
||||||
import scala.collection.immutable.HashMap
|
|
||||||
import scala.concurrent.ExecutionContext.Implicits.global
|
import scala.concurrent.ExecutionContext.Implicits.global
|
||||||
import scala.concurrent.Future
|
import scala.concurrent.Future
|
||||||
import scala.ref.WeakReference
|
import scala.ref.WeakReference
|
||||||
|
@ -57,9 +57,11 @@ class ChatService extends Service {
|
||||||
|
|
||||||
private lazy val Binder = new ChatServiceBinder(this)
|
private lazy val Binder = new ChatServiceBinder(this)
|
||||||
|
|
||||||
private lazy val Crypto = new Crypto(this)
|
private lazy val crypto = new Crypto(this)
|
||||||
|
|
||||||
private lazy val BluetoothInterface = new BluetoothInterface(this, Crypto)
|
private lazy val bluetoothInterface = new BluetoothInterface(this, crypto)
|
||||||
|
|
||||||
|
private val notificationIdGenerator = Stream.from(100)
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* For this (and [[messageListeners]], functions would be useful instead of instances,
|
* For this (and [[messageListeners]], functions would be useful instead of instances,
|
||||||
|
@ -91,15 +93,15 @@ class ChatService extends Service {
|
||||||
registerMessageListener(Database)
|
registerMessageListener(Database)
|
||||||
|
|
||||||
Future {
|
Future {
|
||||||
Crypto.generateLocalKeys()
|
crypto.generateLocalKeys()
|
||||||
|
|
||||||
BluetoothInterface.create()
|
bluetoothInterface.create()
|
||||||
Log.i(Tag, "Service started, address is " + Crypto.getLocalAddress)
|
Log.i(Tag, "Service started, address is " + Crypto.getLocalAddress(this))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override def onDestroy(): Unit = {
|
override def onDestroy(): Unit = {
|
||||||
BluetoothInterface.destroy()
|
bluetoothInterface.destroy()
|
||||||
}
|
}
|
||||||
|
|
||||||
override def onStartCommand(intent: Intent, flags: Int, startId: Int) = Service.START_STICKY
|
override def onStartCommand(intent: Intent, flags: Int, startId: Int) = Service.START_STICKY
|
||||||
|
@ -125,15 +127,15 @@ class ChatService extends Service {
|
||||||
* Sends a new message to the given target address.
|
* Sends a new message to the given target address.
|
||||||
*/
|
*/
|
||||||
def sendTo(target: Address, body: MessageBody): Unit = {
|
def sendTo(target: Address, body: MessageBody): Unit = {
|
||||||
if (!BluetoothInterface.getConnections.contains(target))
|
if (!bluetoothInterface.getConnections.contains(target))
|
||||||
return
|
return
|
||||||
|
|
||||||
val header = new MessageHeader(body.Type, MessageHeader.DefaultHopLimit,
|
val header = new MessageHeader(body.Type, MessageHeader.DefaultHopLimit,
|
||||||
Crypto.getLocalAddress, target, 0, 0)
|
Crypto.getLocalAddress(this), target, 0, 0)
|
||||||
|
|
||||||
val msg = new Message(header, body)
|
val msg = new Message(header, body)
|
||||||
val encrypted = Crypto.encrypt(Crypto.sign(msg))
|
val encrypted = crypto.encrypt(crypto.sign(msg))
|
||||||
BluetoothInterface.send(encrypted)
|
bluetoothInterface.send(encrypted)
|
||||||
onNewMessage(msg)
|
onNewMessage(msg)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -141,8 +143,8 @@ class ChatService extends Service {
|
||||||
* Decrypts and verifies incoming messages, forwards valid ones to [[onNewMessage()]].
|
* Decrypts and verifies incoming messages, forwards valid ones to [[onNewMessage()]].
|
||||||
*/
|
*/
|
||||||
def onMessageReceived(msg: Message): Unit = {
|
def onMessageReceived(msg: Message): Unit = {
|
||||||
val decrypted = Crypto.decrypt(msg)
|
val decrypted = crypto.decrypt(msg)
|
||||||
if (!Crypto.verify(decrypted)) {
|
if (!crypto.verify(decrypted)) {
|
||||||
Log.i(Tag, "Ignoring message with invalid signature from " + msg.Header.Origin)
|
Log.i(Tag, "Ignoring message with invalid signature from " + msg.Header.Origin)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
@ -160,6 +162,25 @@ class ChatService extends Service {
|
||||||
Database.changeContactName(contact)
|
Database.changeContactName(contact)
|
||||||
|
|
||||||
callConnectionListeners()
|
callConnectionListeners()
|
||||||
|
case _: RequestAddContact =>
|
||||||
|
if (msg.Header.Origin == Crypto.getLocalAddress(this))
|
||||||
|
return
|
||||||
|
|
||||||
|
Log.i(Tag, "Remote device " + msg.Header.Origin +
|
||||||
|
" wants to add us as a contact, showing notification")
|
||||||
|
val intent = new Intent(this, classOf[ConfirmAddContactDialog])
|
||||||
|
intent.putExtra(ConfirmAddContactDialog.ExtraContactAddress, msg.Header.Origin.toString)
|
||||||
|
val pi = PendingIntent.getActivity(this, 0, intent,
|
||||||
|
PendingIntent.FLAG_UPDATE_CURRENT)
|
||||||
|
|
||||||
|
val notification = new Notification.Builder(this)
|
||||||
|
.setContentTitle(getString(R.string.notification_friend_request, getUser(msg.Header.Origin)))
|
||||||
|
.setSmallIcon(R.drawable.ic_launcher)
|
||||||
|
.setContentIntent(pi)
|
||||||
|
.setAutoCancel(true)
|
||||||
|
.build()
|
||||||
|
val nm = getSystemService(Context.NOTIFICATION_SERVICE).asInstanceOf[NotificationManager]
|
||||||
|
nm.notify(notificationIdGenerator.iterator.next(), notification)
|
||||||
case _ =>
|
case _ =>
|
||||||
MainHandler.post(new Runnable {
|
MainHandler.post(new Runnable {
|
||||||
override def run(): Unit =
|
override def run(): Unit =
|
||||||
|
@ -182,19 +203,19 @@ class ChatService extends Service {
|
||||||
*/
|
*/
|
||||||
def onConnectionOpened(msg: Message): Boolean = {
|
def onConnectionOpened(msg: Message): Boolean = {
|
||||||
val info = msg.Body.asInstanceOf[ConnectionInfo]
|
val info = msg.Body.asInstanceOf[ConnectionInfo]
|
||||||
val sender = Crypto.calculateAddress(info.key)
|
val sender = crypto.calculateAddress(info.key)
|
||||||
if (sender == Address.Broadcast || sender == Address.Null) {
|
if (sender == Address.Broadcast || sender == Address.Null) {
|
||||||
Log.i(Tag, "Ignoring ConnectionInfo message with invalid sender " + sender)
|
Log.i(Tag, "Ignoring ConnectionInfo message with invalid sender " + sender)
|
||||||
false
|
false
|
||||||
}
|
}
|
||||||
|
|
||||||
if (Crypto.havePublicKey(sender) && !Crypto.verify(msg, Crypto.getPublicKey(sender))) {
|
if (crypto.havePublicKey(sender) && !crypto.verify(msg, crypto.getPublicKey(sender))) {
|
||||||
Log.i(Tag, "Ignoring ConnectionInfo message with invalid signature")
|
Log.i(Tag, "Ignoring ConnectionInfo message with invalid signature")
|
||||||
false
|
false
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!Crypto.havePublicKey(sender)) {
|
if (!crypto.havePublicKey(sender)) {
|
||||||
Crypto.addPublicKey(sender, info.key)
|
crypto.addPublicKey(sender, info.key)
|
||||||
Log.i(Tag, "Added public key for new device " + sender.toString)
|
Log.i(Tag, "Added public key for new device " + sender.toString)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -220,7 +241,7 @@ class ChatService extends Service {
|
||||||
* Returns all direct neighbors.
|
* Returns all direct neighbors.
|
||||||
*/
|
*/
|
||||||
def getConnections: Set[User] = {
|
def getConnections: Set[User] = {
|
||||||
BluetoothInterface.getConnections.map{ address =>
|
bluetoothInterface.getConnections.map{ address =>
|
||||||
(Database.getContacts ++ connections).find(_.Address == address) match {
|
(Database.getContacts ++ connections).find(_.Address == address) match {
|
||||||
case Some(contact) => contact
|
case Some(contact) => contact
|
||||||
case None => new User(address, address.toString)
|
case None => new User(address, address.toString)
|
||||||
|
@ -228,4 +249,7 @@ class ChatService extends Service {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
def getUser(address: Address) =
|
||||||
|
getConnections.find(_.Address == address).getOrElse(new User(address, address.toString))
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -308,6 +308,4 @@ class Crypto(Context: Context) {
|
||||||
new Address(hash)
|
new Address(hash)
|
||||||
}
|
}
|
||||||
|
|
||||||
def getLocalAddress = Crypto.getLocalAddress(Context)
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
Reference in a new issue