Added identicons based on public key when adding new contact.

Also fixed a derp with cases.
This commit is contained in:
Felix Ableitner 2014-11-17 02:58:29 +02:00
parent f31db97230
commit e430608311
6 changed files with 176 additions and 22 deletions

View file

@ -0,0 +1,60 @@
<RelativeLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:padding="10dip">
<ScrollView
android:id="@+id/scrollview"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_centerHorizontal="true"
android:layout_above="@+id/hint">
<RelativeLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:gravity="center"
android:padding="10dip">
<TextView
android:id="@+id/local_identicon_title"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerHorizontal="true"
android:text="@string/local_fingerprint_title"/>
<ImageView
android:id="@+id/local_identicon"
android:layout_width="150dip"
android:layout_height="150dip"
android:layout_centerHorizontal="true"
android:layout_below="@id/local_identicon_title"/>
<TextView
android:id="@+id/remote_identicon_title"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerHorizontal="true"
android:layout_below="@id/local_identicon"/>
<ImageView
android:id="@+id/remote_identicon"
android:layout_width="150dip"
android:layout_height="150dip"
android:layout_centerHorizontal="true"
android:layout_below="@id/remote_identicon_title"/>
</RelativeLayout>
</ScrollView>
<TextView
android:id="@+id/hint"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_centerHorizontal="true"
android:gravity="center"
android:layout_alignParentBottom="true"
android:text="@string/add_contact_dialog_hint"/>
</RelativeLayout>

View file

@ -35,6 +35,15 @@
<!-- 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>
<!-- Text to describe the local device's identicon image -->
<string name="local_fingerprint_title">Your key fingerprint:</string>
<!-- Text to describe the remote device's identicon image. Argument is the contact name -->
<string name="remote_fingerprint_title">%1$s\'s key fingerprint:</string>
<!-- 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>
<!-- Empty text for devices list -->
<string name="no_devices_nearby">No nearby devices found</string>

View file

@ -3,17 +3,17 @@ package com.nutomic.ensichat.activities
import java.util.Date
import android.app.AlertDialog
import android.content.DialogInterface
import android.content.DialogInterface.OnClickListener
import android.content.{Context, DialogInterface}
import android.os.Bundle
import android.view._
import android.widget.AdapterView.OnItemClickListener
import android.widget.{AdapterView, ListView, Toast}
import android.widget._
import com.nutomic.ensichat.R
import com.nutomic.ensichat.bluetooth.ChatService.OnMessageReceivedListener
import com.nutomic.ensichat.bluetooth.{ChatService, Device}
import com.nutomic.ensichat.messages.{Message, RequestAddContactMessage, ResultAddContactMessage}
import com.nutomic.ensichat.util.DevicesAdapter
import com.nutomic.ensichat.messages.{Crypto, Message, RequestAddContactMessage, ResultAddContactMessage}
import com.nutomic.ensichat.util.{DevicesAdapter, IdenticonGenerator}
import scala.collection.SortedSet
@ -29,6 +29,8 @@ class AddContactsActivity extends EnsiChatActivity with ChatService.OnConnection
private lazy val Database = service.database
private lazy val Crypto = new Crypto(this.getFilesDir)
/**
* Map of devices that should be added.
*/
@ -109,8 +111,21 @@ class AddContactsActivity extends EnsiChatActivity with ChatService.OnConnection
}
}
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.getLocalPublicKey, (150, 150), this))
val remoteTitle = view.findViewById(R.id.remote_identicon_title).asInstanceOf[TextView]
remoteTitle.setText(getString(R.string.remote_fingerprint_title, device.Name))
val remote = view.findViewById(R.id.remote_identicon).asInstanceOf[ImageView]
remote.setImageBitmap(
IdenticonGenerator.generate(Crypto.getPublicKey(device.Id), (150, 150), this))
new AlertDialog.Builder(this)
.setTitle(getString(R.string.add_contact_dialog, device.Name))
.setView(view)
.setPositiveButton(android.R.string.yes, onClick)
.setNegativeButton(android.R.string.no, onClick)
.show()
@ -126,7 +141,7 @@ class AddContactsActivity extends EnsiChatActivity with ChatService.OnConnection
messages.foreach(msg => {
if (msg.receiver == service.localDeviceId) {
msg match {
case _: ResultAddContactMessage =>
case _: RequestAddContactMessage =>
// Remote device wants to add us as a contact, show dialog.
val sender = getDevice(msg.sender)
addDeviceDialog(sender)

View file

@ -4,9 +4,9 @@ import java.io._
import android.bluetooth.BluetoothSocket
import android.util.Log
import com.nutomic.ensichat.messages.Message._
import com.nutomic.ensichat.messages.{Crypto, DeviceInfoMessage, Message}
import org.msgpack.ScalaMessagePack
import com.nutomic.ensichat.messages.Message._
/**
* Transfers data between connnected devices.

View file

@ -72,6 +72,15 @@ class Crypto(filesDir: File) {
*/
def havePublicKey(device: Device.ID): Boolean = new File(keyFolder, device.toString).exists()
/**
* Returns the public key for the given device.
*
* @throws RuntimeException If the key does not exist.
*/
def getPublicKey(device: Device.ID): PublicKey = {
loadKey(device.toString, classOf[PublicKey])
}
/**
* Adds a new public key for a remote device.
*

View file

@ -0,0 +1,61 @@
package com.nutomic.ensichat.util
import java.security.{MessageDigest, PublicKey}
import android.content.Context
import android.graphics.Bitmap.Config
import android.graphics.{Bitmap, Canvas, Color}
/**
* Calculates a unique identicon for the given hash.
*
* Based on "Contact Identicons" by David Hamp-Gonsalves (converted from Java to Scala).
* https://github.com/davidhampgonsalves/Contact-Identicons
*/
object IdenticonGenerator {
val Height: Int = 5
val Width: Int = 5
/**
* Generates an identicon for the key.
*
* The identicon size is fixed to [[Height]]x[[Width]].
*
* @param size The size of the bitmap returned.
*/
def generate(key: PublicKey, size: (Int, Int), context: Context): Bitmap = {
// Hash the key.
val digest = MessageDigest.getInstance("SHA-1")
val hash = digest.digest(key.getEncoded)
// Create base image and colors.
var identicon = Bitmap.createBitmap(Width, Height, Config.ARGB_8888)
val background = Color.parseColor("#f0f0f0")
val r = hash(0) & 255
val g = hash(1) & 255
val b = hash(2) & 255
val foreground = Color.argb(255, r, g, b)
// Color pixels.
for (x <- 0 until Width) {
val i = if (x < 3) x else 4 - x
var pixelColor: Int = 0
for (y <- 0 until Height) {
pixelColor = if ((hash(i) >> y & 1) == 1) foreground else background
identicon.setPixel(x, y, pixelColor)
}
}
// Add border.
val bmpWithBorder = Bitmap.createBitmap(12, 12, identicon.getConfig)
val canvas = new Canvas(bmpWithBorder)
canvas.drawColor(background)
identicon = Bitmap.createScaledBitmap(identicon, 10, 10, false)
canvas.drawBitmap(identicon, 1, 1, null)
Bitmap.createScaledBitmap(identicon, size._1, size._2, false)
}
}