parent
212bb8beba
commit
9959453c38
19 changed files with 157 additions and 119 deletions
36
PROTOCOL.md
36
PROTOCOL.md
|
@ -8,7 +8,8 @@ The key words "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT",
|
|||
document are to be interpreted as described in RFC 2119.
|
||||
|
||||
A _node_ is a single device implementing this protocol. Each node has
|
||||
exactly one node address based on its RSA key pair.
|
||||
a 4096 bit RSA key pair. This key pair is used for message signing
|
||||
and encryption. Every node has exactly one node address.
|
||||
|
||||
A _node address_ consists of 32 bytes and is the SHA-256 hash of the
|
||||
node's public key.
|
||||
|
@ -29,17 +30,12 @@ either address.
|
|||
Crypto
|
||||
------
|
||||
|
||||
Every node has a 4096 RSA key pair that is used for message signing
|
||||
and encryption.
|
||||
The message body is always are signed with 'SHA256withRSA'. The
|
||||
signature is written to the 'Encryption Data' part.
|
||||
|
||||
All messages are signed with 'SHA256withRSA'. The signature is written
|
||||
to the 'Encryption Data' part.
|
||||
|
||||
Content messages are encrypted using a random 256 bit AES key. The
|
||||
key is then wrapped using RSA with the sender's private key, and
|
||||
written to the 'Encryption Data' part.
|
||||
|
||||
The node address is the output of 'SHA-256' on the private key.
|
||||
The body of content messages is encrypted using a random 256 bit
|
||||
AES key. The key is then wrapped using RSA with the sender's
|
||||
private key, and the result written to the 'Encryption Data' part.
|
||||
|
||||
|
||||
Routing
|
||||
|
@ -148,13 +144,12 @@ to avoid forwarding the same message multiple times.
|
|||
|
||||
Content-Type is one of those in section Content-Messages.
|
||||
|
||||
Message ID is unique for each message by the same sender. A device MUST NOT
|
||||
ever send two messages with the same Message ID.
|
||||
Message ID is unique for each message by the same sender. A device
|
||||
MUST NOT ever send two messages with the same Message ID.
|
||||
|
||||
Time is the unix timestamp of message sending.
|
||||
|
||||
Only Content Messages have the Content-Type Message ID and Time
|
||||
fields.
|
||||
Only Content Messages have the Message ID and Time fields.
|
||||
|
||||
### Encryption Data
|
||||
|
||||
|
@ -277,7 +272,7 @@ A simple chat message.
|
|||
|
||||
Text the string to be transferred, encoded as UTF-8.
|
||||
|
||||
### UserName (Content-Type = 4)
|
||||
### UserInfo (Content-Type = 4)
|
||||
|
||||
0 1 2 3
|
||||
0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
|
||||
|
@ -288,5 +283,12 @@ Text the string to be transferred, encoded as UTF-8.
|
|||
\ Name (variable length) \
|
||||
/ /
|
||||
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
||||
| Status Length |
|
||||
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
||||
/ /
|
||||
\ Status (variable length) \
|
||||
/ /
|
||||
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
||||
|
||||
Contains the sender's name, which should be used for display to users.
|
||||
Contains the sender's name and status, which should be used for
|
||||
display to users.
|
||||
|
|
|
@ -3,7 +3,7 @@ package com.nutomic.ensichat.protocol
|
|||
import java.util.{Date, GregorianCalendar}
|
||||
|
||||
import android.test.AndroidTestCase
|
||||
import com.nutomic.ensichat.protocol.body.{Text, UserName}
|
||||
import com.nutomic.ensichat.protocol.body.{Text, UserInfo}
|
||||
import com.nutomic.ensichat.protocol.header.ContentHeader
|
||||
import junit.framework.Assert._
|
||||
|
||||
|
@ -97,9 +97,9 @@ class RouterTest extends AndroidTestCase {
|
|||
}
|
||||
|
||||
private def generateMessage(sender: Address, receiver: Address, seqNum: Int): Message = {
|
||||
val header = new ContentHeader(sender, receiver, seqNum, UserName.Type, 5,
|
||||
val header = new ContentHeader(sender, receiver, seqNum, UserInfo.Type, 5,
|
||||
new GregorianCalendar(2014, 6, 10).getTime)
|
||||
new Message(header, new UserName(""))
|
||||
new Message(header, new UserInfo("", ""))
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -2,10 +2,10 @@ package com.nutomic.ensichat.protocol
|
|||
|
||||
object UserTest {
|
||||
|
||||
val u1 = new User(AddressTest.a1, "one")
|
||||
val u1 = new User(AddressTest.a1, "one", "s1")
|
||||
|
||||
val u2 = new User(AddressTest.a2, "two")
|
||||
val u2 = new User(AddressTest.a2, "two", "s2")
|
||||
|
||||
val u3 = new User(AddressTest.a3, "three")
|
||||
val u3 = new User(AddressTest.a3, "three", "s3")
|
||||
|
||||
}
|
|
@ -0,0 +1,15 @@
|
|||
package com.nutomic.ensichat.protocol.body
|
||||
|
||||
import android.test.AndroidTestCase
|
||||
import junit.framework.Assert
|
||||
|
||||
class UserInfoTest extends AndroidTestCase {
|
||||
|
||||
def testWriteRead(): Unit = {
|
||||
val name = new UserInfo("name", "status")
|
||||
val bytes = name.write
|
||||
val body = UserInfo.read(bytes)
|
||||
Assert.assertEquals(name, body.asInstanceOf[UserInfo])
|
||||
}
|
||||
|
||||
}
|
|
@ -1,15 +0,0 @@
|
|||
package com.nutomic.ensichat.protocol.body
|
||||
|
||||
import android.test.AndroidTestCase
|
||||
import junit.framework.Assert
|
||||
|
||||
class UserNameTest extends AndroidTestCase {
|
||||
|
||||
def testWriteRead(): Unit = {
|
||||
val name = new UserName("name")
|
||||
val bytes = name.write
|
||||
val body = UserName.read(bytes)
|
||||
Assert.assertEquals(name, body.asInstanceOf[UserName])
|
||||
}
|
||||
|
||||
}
|
|
@ -6,6 +6,8 @@
|
|||
android:minHeight="?attr/listPreferredItemHeight"
|
||||
android:paddingStart="?android:attr/listPreferredItemPaddingStart"
|
||||
android:paddingEnd="?android:attr/listPreferredItemPaddingEnd"
|
||||
android:paddingLeft="?android:attr/listPreferredItemPaddingLeft"
|
||||
android:paddingRight="?android:attr/listPreferredItemPaddingRight"
|
||||
android:orientation="vertical" >
|
||||
|
||||
<TextView android:id="@android:id/text1"
|
||||
|
@ -13,11 +15,15 @@
|
|||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="8dp"
|
||||
android:textAppearance="?android:attr/textAppearanceListItem"
|
||||
android:textStyle="bold" />
|
||||
android:textStyle="bold"
|
||||
android:singleLine="true"
|
||||
android:ellipsize="end" />
|
||||
|
||||
<TextView android:id="@android:id/text2"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:textAppearance="?android:attr/textAppearanceListItemSmall" />
|
||||
android:textAppearance="?android:attr/textAppearanceListItemSmall"
|
||||
android:singleLine="true"
|
||||
android:ellipsize="end" />
|
||||
|
||||
</LinearLayout>
|
||||
|
|
|
@ -1,6 +1,8 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources translatable="false">
|
||||
|
||||
|
||||
<string name="default_user_status">Let\'s chat!</string>
|
||||
|
||||
<string name="default_scan_interval">15</string>
|
||||
|
||||
<bool name="default_notification_sounds">true</bool>
|
||||
|
|
|
@ -75,6 +75,9 @@
|
|||
<!-- Preference title -->
|
||||
<string name="user_name">Name</string>
|
||||
|
||||
<!-- Preference title -->
|
||||
<string name="user_status">Status</string>
|
||||
|
||||
<!-- Preference title -->
|
||||
<string name="scan_interval_seconds">Scan Interval (seconds)</string>
|
||||
|
||||
|
|
|
@ -6,6 +6,10 @@
|
|||
android:title="@string/user_name"
|
||||
android:key="user_name" />
|
||||
|
||||
<EditTextPreference
|
||||
android:title="@string/user_status"
|
||||
android:key="user_status" />
|
||||
|
||||
<CheckBoxPreference
|
||||
android:title="@string/notification_sounds"
|
||||
android:key="notification_sounds"
|
||||
|
|
|
@ -69,6 +69,7 @@ class FirstStartActivity extends AppCompatActivity with OnEditorActionListener w
|
|||
.edit()
|
||||
.putBoolean(KeyIsFirstStart, false)
|
||||
.putString(SettingsFragment.KeyUserName, username.getText.toString.trim)
|
||||
.putString(SettingsFragment.KeyUserStatus, getString(R.string.default_user_status))
|
||||
.apply()
|
||||
|
||||
startMainActivity()
|
||||
|
|
|
@ -86,12 +86,12 @@ class ChatFragment extends ListFragment with OnClickListener
|
|||
super.onCreate(savedInstanceState)
|
||||
|
||||
if (savedInstanceState != null)
|
||||
address = new Address(savedInstanceState.getByteArray("device"))
|
||||
address = new Address(savedInstanceState.getByteArray("address"))
|
||||
}
|
||||
|
||||
override def onSaveInstanceState(outState: Bundle): Unit = {
|
||||
super.onSaveInstanceState(outState)
|
||||
outState.putByteArray("device", address.bytes)
|
||||
outState.putByteArray("address", address.bytes)
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -6,12 +6,13 @@ import android.preference.{Preference, PreferenceFragment, PreferenceManager}
|
|||
import com.nutomic.ensichat.{BuildConfig, R}
|
||||
import com.nutomic.ensichat.activities.EnsichatActivity
|
||||
import com.nutomic.ensichat.fragments.SettingsFragment._
|
||||
import com.nutomic.ensichat.protocol.body.UserName
|
||||
import com.nutomic.ensichat.protocol.body.UserInfo
|
||||
import com.nutomic.ensichat.util.Database
|
||||
|
||||
object SettingsFragment {
|
||||
|
||||
val KeyUserName = "user_name"
|
||||
val KeyUserStatus = "user_status"
|
||||
val KeyScanInterval = "scan_interval_seconds"
|
||||
val MaxConnections = "max_connections"
|
||||
val Version = "version"
|
||||
|
@ -26,19 +27,22 @@ class SettingsFragment extends PreferenceFragment with OnPreferenceChangeListene
|
|||
private lazy val database = new Database(getActivity)
|
||||
|
||||
private lazy val name = findPreference(KeyUserName)
|
||||
private lazy val status = findPreference(KeyUserStatus)
|
||||
private lazy val scanInterval = findPreference(KeyScanInterval)
|
||||
private lazy val maxConnections = findPreference(MaxConnections)
|
||||
private lazy val version = findPreference(Version)
|
||||
|
||||
private lazy val prefs = PreferenceManager.getDefaultSharedPreferences(getActivity)
|
||||
|
||||
override def onCreate(savedInstanceState: Bundle): Unit = {
|
||||
super.onCreate(savedInstanceState)
|
||||
|
||||
addPreferencesFromResource(R.xml.settings)
|
||||
|
||||
val prefs = PreferenceManager.getDefaultSharedPreferences(getActivity)
|
||||
|
||||
name.setSummary(prefs.getString(KeyUserName, ""))
|
||||
name.setOnPreferenceChangeListener(this)
|
||||
status.setSummary(prefs.getString(KeyUserStatus, ""))
|
||||
status.setOnPreferenceChangeListener(this)
|
||||
|
||||
scanInterval.setOnPreferenceChangeListener(this)
|
||||
scanInterval.setSummary(prefs.getString(
|
||||
|
@ -59,9 +63,11 @@ class SettingsFragment extends PreferenceFragment with OnPreferenceChangeListene
|
|||
* Updates summary, sends updated name to contacts.
|
||||
*/
|
||||
override def onPreferenceChange(preference: Preference, newValue: AnyRef): Boolean = {
|
||||
if (preference.getKey == KeyUserName) {
|
||||
val service = getActivity.asInstanceOf[EnsichatActivity].service
|
||||
database.getContacts.foreach(c => service.sendTo(c.address, new UserName(newValue.toString)))
|
||||
preference.getKey match {
|
||||
case KeyUserName | KeyUserStatus =>
|
||||
val service = getActivity.asInstanceOf[EnsichatActivity].service
|
||||
val ui = new UserInfo(prefs.getString(KeyUserName, ""), prefs.getString(KeyUserStatus, ""))
|
||||
database.getContacts.foreach(c => service.sendTo(c.address, ui))
|
||||
}
|
||||
preference.setSummary(newValue.toString)
|
||||
true
|
||||
|
|
|
@ -13,7 +13,7 @@ import com.nutomic.ensichat.activities.MainActivity
|
|||
import com.nutomic.ensichat.bluetooth.BluetoothInterface
|
||||
import com.nutomic.ensichat.fragments.SettingsFragment
|
||||
import com.nutomic.ensichat.protocol.ChatService.{OnConnectionsChangedListener, OnMessageReceivedListener}
|
||||
import com.nutomic.ensichat.protocol.body.{ConnectionInfo, MessageBody, UserName}
|
||||
import com.nutomic.ensichat.protocol.body.{ConnectionInfo, MessageBody, UserInfo}
|
||||
import com.nutomic.ensichat.protocol.header.ContentHeader
|
||||
import com.nutomic.ensichat.util.{AddContactsHandler, Database, NotificationHandler}
|
||||
|
||||
|
@ -192,11 +192,11 @@ class ChatService extends Service {
|
|||
* Handles all (locally and remotely sent) new messages.
|
||||
*/
|
||||
private def onNewMessage(msg: Message): Unit = msg.body match {
|
||||
case name: UserName =>
|
||||
val contact = new User(msg.header.origin, name.name)
|
||||
case ui: UserInfo =>
|
||||
val contact = new User(msg.header.origin, ui.name, ui.status)
|
||||
knownUsers += contact
|
||||
if (database.getContact(msg.header.origin).nonEmpty)
|
||||
database.changeContactName(contact)
|
||||
database.updateContact(contact)
|
||||
|
||||
callConnectionListeners()
|
||||
case _ =>
|
||||
|
@ -243,8 +243,8 @@ class ChatService extends Service {
|
|||
}
|
||||
|
||||
Log.i(Tag, "Node " + sender + " connected")
|
||||
val name = preferences.getString(SettingsFragment.KeyUserName, "")
|
||||
sendTo(sender, new UserName(name))
|
||||
sendTo(sender, new UserInfo(preferences.getString(SettingsFragment.KeyUserName, ""),
|
||||
preferences.getString(SettingsFragment.KeyUserStatus, "")))
|
||||
callConnectionListeners()
|
||||
true
|
||||
}
|
||||
|
@ -263,6 +263,6 @@ class ChatService extends Service {
|
|||
btInterface.getConnections
|
||||
|
||||
def getUser(address: Address) =
|
||||
knownUsers.find(_.address == address).getOrElse(new User(address, address.toString))
|
||||
knownUsers.find(_.address == address).getOrElse(new User(address, address.toString, ""))
|
||||
|
||||
}
|
||||
|
|
|
@ -257,7 +257,7 @@ class Crypto(context: Context) {
|
|||
case RequestAddContact.Type => RequestAddContact.read(decrypted)
|
||||
case ResultAddContact.Type => ResultAddContact.read(decrypted)
|
||||
case Text.Type => Text.read(decrypted)
|
||||
case UserName.Type => UserName.read(decrypted)
|
||||
case UserInfo.Type => UserInfo.read(decrypted)
|
||||
}
|
||||
new Message(msg.header, msg.crypto, body)
|
||||
}
|
||||
|
|
|
@ -1,3 +1,3 @@
|
|||
package com.nutomic.ensichat.protocol
|
||||
|
||||
case class User(address: Address, name: String)
|
||||
case class User(address: Address, name: String, status: String)
|
|
@ -0,0 +1,53 @@
|
|||
package com.nutomic.ensichat.protocol.body
|
||||
|
||||
import java.nio.ByteBuffer
|
||||
import com.nutomic.ensichat.protocol.Message
|
||||
import com.nutomic.ensichat.util.BufferUtils
|
||||
|
||||
object UserInfo {
|
||||
|
||||
val Type = 7
|
||||
|
||||
/**
|
||||
* Constructs [[UserInfo]] instance from byte array.
|
||||
*/
|
||||
def read(array: Array[Byte]): UserInfo = {
|
||||
val bb = ByteBuffer.wrap(array)
|
||||
new UserInfo(getValue(bb), getValue(bb))
|
||||
}
|
||||
|
||||
private def getValue(bb: ByteBuffer): String = {
|
||||
val length = BufferUtils.getUnsignedInt(bb).toInt
|
||||
val bytes = new Array[Byte](length)
|
||||
bb.get(bytes, 0, length)
|
||||
new String(bytes, Message.Charset)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds display name and status of the sender.
|
||||
*/
|
||||
case class UserInfo(name: String, status: String) extends MessageBody {
|
||||
|
||||
override def protocolType = -1
|
||||
|
||||
override def contentType = UserInfo.Type
|
||||
|
||||
override def write: Array[Byte] = {
|
||||
val b = ByteBuffer.allocate(length)
|
||||
put(b, name)
|
||||
put(b, status)
|
||||
b.array()
|
||||
}
|
||||
|
||||
def put(b: ByteBuffer, value: String): ByteBuffer = {
|
||||
val bytes = value.getBytes(Message.Charset)
|
||||
BufferUtils.putUnsignedInt(b, bytes.length)
|
||||
b.put(bytes)
|
||||
}
|
||||
|
||||
override def length = 8 + name.getBytes(Message.Charset).length +
|
||||
status.getBytes(Message.Charset).length
|
||||
|
||||
}
|
|
@ -1,43 +0,0 @@
|
|||
package com.nutomic.ensichat.protocol.body
|
||||
|
||||
import java.nio.ByteBuffer
|
||||
import com.nutomic.ensichat.protocol.Message
|
||||
import com.nutomic.ensichat.util.BufferUtils
|
||||
|
||||
object UserName {
|
||||
|
||||
val Type = 7
|
||||
|
||||
/**
|
||||
* Constructs [[UserName]] instance from byte array.
|
||||
*/
|
||||
def read(array: Array[Byte]): UserName = {
|
||||
val b = ByteBuffer.wrap(array)
|
||||
val length = BufferUtils.getUnsignedInt(b).toInt
|
||||
val bytes = new Array[Byte](length)
|
||||
b.get(bytes, 0, length)
|
||||
new UserName(new String(bytes, Message.Charset))
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds the display name of the sender.
|
||||
*/
|
||||
case class UserName(name: String) extends MessageBody {
|
||||
|
||||
override def protocolType = -1
|
||||
|
||||
override def contentType = UserName.Type
|
||||
|
||||
override def write: Array[Byte] = {
|
||||
val b = ByteBuffer.allocate(length)
|
||||
val bytes = name.getBytes(Message.Charset)
|
||||
BufferUtils.putUnsignedInt(b, bytes.length)
|
||||
b.put(bytes)
|
||||
b.array()
|
||||
}
|
||||
|
||||
override def length = 4 + name.getBytes(Message.Charset).length
|
||||
|
||||
}
|
|
@ -19,7 +19,7 @@ object Database {
|
|||
|
||||
private val DatabaseName = "message_store.db"
|
||||
|
||||
private val DatabaseVersion = 1
|
||||
private val DatabaseVersion = 2
|
||||
|
||||
// NOTE: We could make origin/target foreign keys to contacts, but:
|
||||
// - they don't change anyway
|
||||
|
@ -35,7 +35,8 @@ object Database {
|
|||
private val CreateContactsTable = "CREATE TABLE contacts(" +
|
||||
"_id INTEGER PRIMARY KEY," +
|
||||
"address TEXT NOT NULL," +
|
||||
"name TEXT NOT NULL)"
|
||||
"name TEXT NOT NULL," +
|
||||
"status TEXT NOT NULL)"
|
||||
|
||||
}
|
||||
|
||||
|
@ -97,12 +98,13 @@ class Database(context: Context)
|
|||
* Returns all contacts of this user.
|
||||
*/
|
||||
def getContacts: Set[User] = {
|
||||
val c = getReadableDatabase.query(true, "contacts", Array("address", "name"), "", Array(),
|
||||
val c = getReadableDatabase.query(true, "contacts", Array("address", "name", "status"), "", Array(),
|
||||
null, null, null, null)
|
||||
var contacts = Set[User]()
|
||||
while (c.moveToNext()) {
|
||||
contacts += new User(new Address(c.getString(c.getColumnIndex("address"))),
|
||||
c.getString(c.getColumnIndex("name")))
|
||||
c.getString(c.getColumnIndex("name")),
|
||||
c.getString(c.getColumnIndex("status")))
|
||||
}
|
||||
c.close()
|
||||
contacts
|
||||
|
@ -112,12 +114,13 @@ class Database(context: Context)
|
|||
* Returns the contact with the given address if it exists.
|
||||
*/
|
||||
def getContact(address: Address): Option[User] = {
|
||||
val c = getReadableDatabase.query(true, "contacts", Array("address", "name"), "address = ?",
|
||||
val c = getReadableDatabase.query(true, "contacts", Array("address", "name", "status"), "address = ?",
|
||||
Array(address.toString), null, null, null, null)
|
||||
if (c.getCount != 0) {
|
||||
c.moveToNext()
|
||||
val s = Option(new User(new Address(c.getString(c.getColumnIndex("address"))),
|
||||
c.getString(c.getColumnIndex("name"))))
|
||||
c.getString(c.getColumnIndex("name")),
|
||||
c.getString(c.getColumnIndex("status"))))
|
||||
c.close()
|
||||
s
|
||||
} else {
|
||||
|
@ -132,14 +135,16 @@ class Database(context: Context)
|
|||
def addContact(contact: User): Unit = {
|
||||
val cv = new ContentValues()
|
||||
cv.put("address", contact.address.toString)
|
||||
cv.put("name", contact.name.toString)
|
||||
cv.put("name", contact.name)
|
||||
cv.put("status", contact.status)
|
||||
getWritableDatabase.insert("contacts", null, cv)
|
||||
contactsUpdated()
|
||||
}
|
||||
|
||||
def changeContactName(contact: User): Unit = {
|
||||
def updateContact(contact: User): Unit = {
|
||||
val cv = new ContentValues()
|
||||
cv.put("name", contact.name.toString)
|
||||
cv.put("name", contact.name)
|
||||
cv.put("status", contact.status)
|
||||
getWritableDatabase.update("contacts", cv, "address = ?", Array(contact.address.toString))
|
||||
contactsUpdated()
|
||||
}
|
||||
|
@ -150,6 +155,12 @@ class Database(context: Context)
|
|||
}
|
||||
|
||||
override def onUpgrade(db: SQLiteDatabase, oldVersion: Int, newVersion: Int): Unit = {
|
||||
if (oldVersion < 2) {
|
||||
db.execSQL("ALTER TABLE contacts ADD COLUMN status TEXT")
|
||||
val cv = new ContentValues()
|
||||
cv.put("status", "")
|
||||
db.update("contacts", cv, null, null)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -3,10 +3,9 @@ package com.nutomic.ensichat.util
|
|||
import android.content.Context
|
||||
import android.view.{LayoutInflater, View, ViewGroup}
|
||||
import android.widget.{ArrayAdapter, TextView}
|
||||
import com.nutomic.ensichat.R
|
||||
import com.nutomic.ensichat.bluetooth.Device
|
||||
import com.nutomic.ensichat.protocol.User
|
||||
import com.nutomic.ensichat.protocol.body.Text
|
||||
import com.nutomic.ensichat.R
|
||||
|
||||
/**
|
||||
* Displays [[Device]]s in ListView.
|
||||
|
@ -14,8 +13,6 @@ import com.nutomic.ensichat.R
|
|||
class UsersAdapter(context: Context) extends
|
||||
ArrayAdapter[User](context, 0) {
|
||||
|
||||
private lazy val database = new Database(context)
|
||||
|
||||
override def getView(position: Int, convertView: View, parent: ViewGroup): View = {
|
||||
val view =
|
||||
if (convertView == null) {
|
||||
|
@ -28,11 +25,7 @@ class UsersAdapter(context: Context) extends
|
|||
val summary = view.findViewById(android.R.id.text2).asInstanceOf[TextView]
|
||||
val item = getItem(position)
|
||||
title.setText(item.name)
|
||||
database.getMessages(item.address, 1)
|
||||
.map(_.body)
|
||||
.foreach {
|
||||
case m: Text => summary.setText(m.text)
|
||||
}
|
||||
summary.setText(item.status)
|
||||
view
|
||||
}
|
||||
|
||||
|
|
Reference in a new issue