Added identicons based on public key when adding new contact.
Also fixed a derp with cases.
This commit is contained in:
parent
f31db97230
commit
e430608311
6 changed files with 176 additions and 22 deletions
60
app/src/main/res/layout/dialog_add_contact.xml
Normal file
60
app/src/main/res/layout/dialog_add_contact.xml
Normal 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>
|
|
@ -35,6 +35,15 @@
|
||||||
<!-- 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>
|
||||||
|
|
||||||
|
<!-- 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 -->
|
<!-- Empty text for devices list -->
|
||||||
<string name="no_devices_nearby">No nearby devices found</string>
|
<string name="no_devices_nearby">No nearby devices found</string>
|
||||||
|
|
||||||
|
|
|
@ -3,17 +3,17 @@ package com.nutomic.ensichat.activities
|
||||||
import java.util.Date
|
import java.util.Date
|
||||||
|
|
||||||
import android.app.AlertDialog
|
import android.app.AlertDialog
|
||||||
import android.content.DialogInterface
|
|
||||||
import android.content.DialogInterface.OnClickListener
|
import android.content.DialogInterface.OnClickListener
|
||||||
|
import android.content.{Context, DialogInterface}
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import android.view._
|
import android.view._
|
||||||
import android.widget.AdapterView.OnItemClickListener
|
import android.widget.AdapterView.OnItemClickListener
|
||||||
import android.widget.{AdapterView, ListView, Toast}
|
import android.widget._
|
||||||
import com.nutomic.ensichat.R
|
import com.nutomic.ensichat.R
|
||||||
import com.nutomic.ensichat.bluetooth.ChatService.OnMessageReceivedListener
|
import com.nutomic.ensichat.bluetooth.ChatService.OnMessageReceivedListener
|
||||||
import com.nutomic.ensichat.bluetooth.{ChatService, Device}
|
import com.nutomic.ensichat.bluetooth.{ChatService, Device}
|
||||||
import com.nutomic.ensichat.messages.{Message, RequestAddContactMessage, ResultAddContactMessage}
|
import com.nutomic.ensichat.messages.{Crypto, Message, RequestAddContactMessage, ResultAddContactMessage}
|
||||||
import com.nutomic.ensichat.util.DevicesAdapter
|
import com.nutomic.ensichat.util.{DevicesAdapter, IdenticonGenerator}
|
||||||
|
|
||||||
import scala.collection.SortedSet
|
import scala.collection.SortedSet
|
||||||
|
|
||||||
|
@ -29,6 +29,8 @@ class AddContactsActivity extends EnsiChatActivity with ChatService.OnConnection
|
||||||
|
|
||||||
private lazy val Database = service.database
|
private lazy val Database = service.database
|
||||||
|
|
||||||
|
private lazy val Crypto = new Crypto(this.getFilesDir)
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Map of devices that should be added.
|
* 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)
|
new AlertDialog.Builder(this)
|
||||||
.setTitle(getString(R.string.add_contact_dialog, device.Name))
|
.setTitle(getString(R.string.add_contact_dialog, device.Name))
|
||||||
|
.setView(view)
|
||||||
.setPositiveButton(android.R.string.yes, onClick)
|
.setPositiveButton(android.R.string.yes, onClick)
|
||||||
.setNegativeButton(android.R.string.no, onClick)
|
.setNegativeButton(android.R.string.no, onClick)
|
||||||
.show()
|
.show()
|
||||||
|
@ -126,7 +141,7 @@ class AddContactsActivity extends EnsiChatActivity with ChatService.OnConnection
|
||||||
messages.foreach(msg => {
|
messages.foreach(msg => {
|
||||||
if (msg.receiver == service.localDeviceId) {
|
if (msg.receiver == service.localDeviceId) {
|
||||||
msg match {
|
msg match {
|
||||||
case _: ResultAddContactMessage =>
|
case _: RequestAddContactMessage =>
|
||||||
// Remote device wants to add us as a contact, show dialog.
|
// Remote device wants to add us as a contact, show dialog.
|
||||||
val sender = getDevice(msg.sender)
|
val sender = getDevice(msg.sender)
|
||||||
addDeviceDialog(sender)
|
addDeviceDialog(sender)
|
||||||
|
|
|
@ -4,9 +4,9 @@ import java.io._
|
||||||
|
|
||||||
import android.bluetooth.BluetoothSocket
|
import android.bluetooth.BluetoothSocket
|
||||||
import android.util.Log
|
import android.util.Log
|
||||||
|
import com.nutomic.ensichat.messages.Message._
|
||||||
import com.nutomic.ensichat.messages.{Crypto, DeviceInfoMessage, Message}
|
import com.nutomic.ensichat.messages.{Crypto, DeviceInfoMessage, Message}
|
||||||
import org.msgpack.ScalaMessagePack
|
import org.msgpack.ScalaMessagePack
|
||||||
import com.nutomic.ensichat.messages.Message._
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Transfers data between connnected devices.
|
* Transfers data between connnected devices.
|
||||||
|
|
|
@ -72,6 +72,15 @@ class Crypto(filesDir: File) {
|
||||||
*/
|
*/
|
||||||
def havePublicKey(device: Device.ID): Boolean = new File(keyFolder, device.toString).exists()
|
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.
|
* Adds a new public key for a remote device.
|
||||||
*
|
*
|
||||||
|
|
|
@ -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)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
Reference in a new issue