diff --git a/app/src/main/java/i2p/bote/android/EmailListAdapter.java b/app/src/main/java/i2p/bote/android/EmailListAdapter.java index eace625..fbaa668 100644 --- a/app/src/main/java/i2p/bote/android/EmailListAdapter.java +++ b/app/src/main/java/i2p/bote/android/EmailListAdapter.java @@ -84,6 +84,10 @@ public class EmailListAdapter extends ArrayAdapter { Bitmap pic = BoteHelper.getPictureForAddress(otherAddress); if (pic != null) picture.setImageBitmap(pic); + else if (!email.isAnonymous()) { + ViewGroup.LayoutParams lp = picture.getLayoutParams(); + picture.setImageBitmap(BoteHelper.getIdenticonForAddress(otherAddress, lp.width, lp.height)); + } subject.setText(email.getSubject()); address.setText(BoteHelper.getNameAndShortDestination(otherAddress)); diff --git a/app/src/main/java/i2p/bote/android/ViewEmailFragment.java b/app/src/main/java/i2p/bote/android/ViewEmailFragment.java index 8630319..093405f 100644 --- a/app/src/main/java/i2p/bote/android/ViewEmailFragment.java +++ b/app/src/main/java/i2p/bote/android/ViewEmailFragment.java @@ -1,15 +1,5 @@ package i2p.bote.android; -import java.io.IOException; -import java.security.GeneralSecurityException; -import java.text.DateFormat; - -import javax.mail.Address; -import javax.mail.MessagingException; - -import i2p.bote.android.util.BoteHelper; -import i2p.bote.email.Email; -import i2p.bote.fileencryption.PasswordException; import android.content.Intent; import android.graphics.Bitmap; import android.os.Bundle; @@ -25,6 +15,17 @@ import android.widget.LinearLayout; import android.widget.TableRow; import android.widget.TextView; +import java.io.IOException; +import java.security.GeneralSecurityException; +import java.text.DateFormat; + +import javax.mail.Address; +import javax.mail.MessagingException; + +import i2p.bote.android.util.BoteHelper; +import i2p.bote.email.Email; +import i2p.bote.fileencryption.PasswordException; + public class ViewEmailFragment extends Fragment { private String mFolderName; private String mMessageId; @@ -89,6 +90,10 @@ public class ViewEmailFragment extends Fragment { Bitmap pic = BoteHelper.getPictureForAddress(fromAddress); if (pic != null) picture.setImageBitmap(pic); + else if (!email.isAnonymous()) { + ViewGroup.LayoutParams lp = picture.getLayoutParams(); + picture.setImageBitmap(BoteHelper.getIdenticonForAddress(fromAddress, lp.width, lp.height)); + } sender.setText(BoteHelper.getDisplayAddress(fromAddress)); diff --git a/app/src/main/java/i2p/bote/android/addressbook/ContactAdapter.java b/app/src/main/java/i2p/bote/android/addressbook/ContactAdapter.java index 22cd7ed..a89e397 100644 --- a/app/src/main/java/i2p/bote/android/addressbook/ContactAdapter.java +++ b/app/src/main/java/i2p/bote/android/addressbook/ContactAdapter.java @@ -1,11 +1,5 @@ package i2p.bote.android.addressbook; -import i2p.bote.android.R; -import i2p.bote.android.util.BoteHelper; -import i2p.bote.packet.dht.Contact; - -import java.util.SortedSet; - import android.content.Context; import android.view.LayoutInflater; import android.view.View; @@ -14,6 +8,12 @@ import android.widget.ArrayAdapter; import android.widget.ImageView; import android.widget.TextView; +import java.util.SortedSet; + +import i2p.bote.android.R; +import i2p.bote.android.util.BoteHelper; +import i2p.bote.packet.dht.Contact; + public class ContactAdapter extends ArrayAdapter { private final LayoutInflater mInflater; @@ -42,6 +42,10 @@ public class ContactAdapter extends ArrayAdapter { String pic = contact.getPictureBase64(); if (pic != null && !pic.isEmpty()) picture.setImageBitmap(BoteHelper.decodePicture(pic)); + else { + ViewGroup.LayoutParams lp = picture.getLayoutParams(); + picture.setImageBitmap(BoteHelper.getIdenticonForAddress(contact.getBase64Dest(), lp.width, lp.height)); + } name.setText(contact.getName()); diff --git a/app/src/main/java/i2p/bote/android/addressbook/ViewContactFragment.java b/app/src/main/java/i2p/bote/android/addressbook/ViewContactFragment.java index db6f8b1..ca7719c 100644 --- a/app/src/main/java/i2p/bote/android/addressbook/ViewContactFragment.java +++ b/app/src/main/java/i2p/bote/android/addressbook/ViewContactFragment.java @@ -95,6 +95,10 @@ public class ViewContactFragment extends Fragment { Bitmap picture = BoteHelper.decodePicture(mContact.getPictureBase64()); if (picture != null) mContactPicture.setImageBitmap(picture); + else { + ViewGroup.LayoutParams lp = mContactPicture.getLayoutParams(); + mContactPicture.setImageBitmap(BoteHelper.getIdenticonForAddress(mDestination, lp.width, lp.height)); + } mNameField.setText(mContact.getName()); mTextField.setText(mContact.getText()); diff --git a/app/src/main/java/i2p/bote/android/config/ViewIdentityFragment.java b/app/src/main/java/i2p/bote/android/config/ViewIdentityFragment.java index e8d0245..5a78545 100644 --- a/app/src/main/java/i2p/bote/android/config/ViewIdentityFragment.java +++ b/app/src/main/java/i2p/bote/android/config/ViewIdentityFragment.java @@ -106,6 +106,10 @@ public class ViewIdentityFragment extends Fragment { Bitmap picture = BoteHelper.decodePicture(mIdentity.getPictureBase64()); if (picture != null) mIdentityPicture.setImageBitmap(picture); + else { + ViewGroup.LayoutParams lp = mIdentityPicture.getLayoutParams(); + mIdentityPicture.setImageBitmap(BoteHelper.getIdenticonForAddress(mKey, lp.width, lp.height)); + } mNameField.setText(mIdentity.getPublicName()); mDescField.setText(mIdentity.getDescription()); diff --git a/app/src/main/java/i2p/bote/android/service/BoteService.java b/app/src/main/java/i2p/bote/android/service/BoteService.java index 25e0ac3..7c921c4 100644 --- a/app/src/main/java/i2p/bote/android/service/BoteService.java +++ b/app/src/main/java/i2p/bote/android/service/BoteService.java @@ -1,29 +1,5 @@ package i2p.bote.android.service; -import java.io.IOException; -import java.security.GeneralSecurityException; -import java.util.List; - -import javax.mail.MessagingException; - -import net.i2p.android.router.service.IRouterState; -import net.i2p.android.router.service.IRouterStateCallback; -import net.i2p.android.router.service.State; -import net.i2p.router.Router; -import net.i2p.router.RouterContext; -import net.i2p.router.RouterLaunch; -import i2p.bote.I2PBote; -import i2p.bote.android.EmailListActivity; -import i2p.bote.android.R; -import i2p.bote.android.ViewEmailActivity; -import i2p.bote.android.service.Init.RouterChoice; -import i2p.bote.android.util.BoteHelper; -import i2p.bote.email.Email; -import i2p.bote.fileencryption.PasswordException; -import i2p.bote.folder.EmailFolder; -import i2p.bote.folder.NewEmailListener; -import i2p.bote.network.NetworkStatusListener; - import android.app.NotificationManager; import android.app.PendingIntent; import android.app.Service; @@ -36,6 +12,31 @@ import android.os.IBinder; import android.os.RemoteException; import android.support.v4.app.NotificationCompat; +import net.i2p.android.router.service.IRouterState; +import net.i2p.android.router.service.IRouterStateCallback; +import net.i2p.android.router.service.State; +import net.i2p.router.Router; +import net.i2p.router.RouterContext; +import net.i2p.router.RouterLaunch; + +import java.io.IOException; +import java.security.GeneralSecurityException; +import java.util.List; + +import javax.mail.MessagingException; + +import i2p.bote.I2PBote; +import i2p.bote.android.EmailListActivity; +import i2p.bote.android.R; +import i2p.bote.android.ViewEmailActivity; +import i2p.bote.android.service.Init.RouterChoice; +import i2p.bote.android.util.BoteHelper; +import i2p.bote.email.Email; +import i2p.bote.fileencryption.PasswordException; +import i2p.bote.folder.EmailFolder; +import i2p.bote.folder.NewEmailListener; +import i2p.bote.network.NetworkStatusListener; + public class BoteService extends Service implements NetworkStatusListener, NewEmailListener { public static final String ROUTER_CHOICE = "router_choice"; public static final int NOTIF_ID_SERVICE = 8073; @@ -235,14 +236,17 @@ public class BoteService extends Service implements NetworkStatusListener, NewEm case 1: Email email = newEmails.get(0); - Bitmap picture = BoteHelper.getPictureForAddress(email.getOneFromAddress()); + String fromAddress = email.getOneFromAddress(); + Bitmap picture = BoteHelper.getPictureForAddress(fromAddress); if (picture != null) b.setLargeIcon(picture); + else if (!email.isAnonymous()) + b.setLargeIcon(BoteHelper.getIdenticonForAddress(fromAddress, 56, 56)); // TODO fix size else b.setSmallIcon(R.drawable.ic_contact_picture); b.setContentTitle(BoteHelper.getNameAndShortDestination( - email.getOneFromAddress())); + fromAddress)); b.setContentText(email.getSubject()); Intent vei = new Intent(this, ViewEmailActivity.class); diff --git a/app/src/main/java/i2p/bote/android/util/BoteHelper.java b/app/src/main/java/i2p/bote/android/util/BoteHelper.java index f72aac6..655fd83 100644 --- a/app/src/main/java/i2p/bote/android/util/BoteHelper.java +++ b/app/src/main/java/i2p/bote/android/util/BoteHelper.java @@ -1,15 +1,5 @@ package i2p.bote.android.util; -import java.io.ByteArrayOutputStream; -import java.io.IOException; -import java.security.GeneralSecurityException; -import java.util.Iterator; -import java.util.List; - -import javax.mail.Address; -import javax.mail.Flags.Flag; -import javax.mail.MessagingException; - import android.app.AlertDialog; import android.app.ProgressDialog; import android.content.Context; @@ -18,6 +8,7 @@ import android.content.res.Resources; import android.graphics.Bitmap; import android.graphics.Bitmap.CompressFormat; import android.graphics.BitmapFactory; +import android.graphics.Canvas; import android.graphics.drawable.Drawable; import android.os.AsyncTask; import android.view.LayoutInflater; @@ -29,6 +20,16 @@ import android.widget.TextView; import com.lambdaworks.codec.Base64; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.security.GeneralSecurityException; +import java.util.Iterator; +import java.util.List; + +import javax.mail.Address; +import javax.mail.Flags.Flag; +import javax.mail.MessagingException; + import i2p.bote.android.R; import i2p.bote.email.Email; import i2p.bote.email.EmailDestination; @@ -38,6 +39,7 @@ import i2p.bote.folder.EmailFolder; import i2p.bote.folder.Outbox.EmailStatus; import i2p.bote.packet.dht.Contact; import i2p.bote.util.GeneralHelper; +import im.delight.android.identicons.Identicon; public class BoteHelper extends GeneralHelper { /** @@ -172,6 +174,17 @@ public class BoteHelper extends GeneralHelper { return new String(Base64.encode(baos.toByteArray())); } + public static Bitmap getIdenticonForAddress(String address, int width, int height) { + String base64dest = extractEmailDestination(address); + Bitmap bitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888); + Canvas canvas = new Canvas(bitmap); + Identicon identicon = new Identicon(); + identicon.show(base64dest == null ? address : base64dest); + identicon.updateSize(canvas.getWidth(), canvas.getHeight()); + identicon.draw(canvas); + return bitmap; + } + public static boolean isSentEmail(Email email) throws PasswordException, IOException, GeneralSecurityException, MessagingException { // Is the sender anonymous and we are not the recipient? if (email.isAnonymous()) { diff --git a/app/src/main/java/im/delight/android/identicons/Identicon.java b/app/src/main/java/im/delight/android/identicons/Identicon.java new file mode 100644 index 0000000..1da0361 --- /dev/null +++ b/app/src/main/java/im/delight/android/identicons/Identicon.java @@ -0,0 +1,35 @@ +package im.delight.android.identicons; + +import android.graphics.Color; + +public class Identicon extends IdenticonBase { + private static final int CENTER_COLUMN_INDEX = 3; + + @Override + protected int getRowCount() { + return 5; + } + + @Override + protected int getColumnCount() { + return 5; + } + + protected int getSymmetricColumnIndex(int row) { + if (row < CENTER_COLUMN_INDEX) { + return row; + } else { + return getColumnCount() - row - 1; + } + } + + @Override + protected boolean isCellVisible(int row, int column) { + return getByte(3 + row * CENTER_COLUMN_INDEX + getSymmetricColumnIndex(column)) >= 0; + } + + @Override + protected int getIconColor() { + return Color.rgb(getByte(0) + 128, getByte(1) + 128, getByte(2) + 128); + } +} diff --git a/app/src/main/java/im/delight/android/identicons/IdenticonBase.java b/app/src/main/java/im/delight/android/identicons/IdenticonBase.java new file mode 100644 index 0000000..7d6f904 --- /dev/null +++ b/app/src/main/java/im/delight/android/identicons/IdenticonBase.java @@ -0,0 +1,117 @@ +package im.delight.android.identicons; + +import android.graphics.Canvas; +import android.graphics.Color; +import android.graphics.Paint; + +import java.security.MessageDigest; + +public abstract class IdenticonBase { + private static final String HASH_ALGORITHM = "SHA-256"; + + + private final int mRowCount; + private final int mColumnCount; + private final Paint mPaint; + private volatile int mCellWidth; + private volatile int mCellHeight; + private volatile byte[] mHash; + private volatile int[][] mColors; + private volatile boolean mReady; + + public IdenticonBase() { + mRowCount = getRowCount(); + mColumnCount = getColumnCount(); + mPaint = new Paint(); + + mPaint.setStyle(Paint.Style.FILL); + mPaint.setAntiAlias(true); + mPaint.setDither(true); + } + + public static byte[] getHash(String input) { + byte[] mHash; + // if the input was null + if (input == null) { + // we can't create a hash value and have nothing to show (draw to the view) + mHash = null; + } + // if the input was a proper string (non-null) + else { + // generate a hash from the string to get unique but deterministic byte values + try { + final MessageDigest digest = java.security.MessageDigest.getInstance(HASH_ALGORITHM); + digest.update(input.getBytes()); + mHash = digest.digest(); + } catch (Exception e) { + mHash = null; + } + } + return mHash; + } + + protected void setupColors() { + mColors = new int[mRowCount][mColumnCount]; + int colorVisible = getIconColor(); + + for (int r = 0; r < mRowCount; r++) { + for (int c = 0; c < mColumnCount; c++) { + if (isCellVisible(r, c)) { + mColors[r][c] = colorVisible; + } else { + mColors[r][c] = Color.TRANSPARENT; + } + } + } + } + + public void show(String input) { + if (input != null) { + mHash = IdenticonBase.getHash(input); + } else { + mHash = null; + } + // set up the cell colors according to the input that was provided via show(...) + setupColors(); + + // this view may now be drawn (and thus must be re-drawn) + mReady = true; + } + + public byte getByte(int index) { + if (mHash == null) { + return -128; + } else { + return mHash[index % mHash.length]; + } + } + + abstract protected int getRowCount(); + + abstract protected int getColumnCount(); + + abstract protected boolean isCellVisible(int row, int column); + + abstract protected int getIconColor(); + + public void updateSize(int w, int h) { + mCellWidth = w / mColumnCount; + mCellHeight = h / mRowCount; + } + + public void draw(Canvas canvas) { + if (mReady) { + int x, y; + for (int r = 0; r < mRowCount; r++) { + for (int c = 0; c < mColumnCount; c++) { + x = mCellWidth * c; + y = mCellHeight * r; + + mPaint.setColor(mColors[r][c]); + + canvas.drawRect(x, y + mCellHeight, x + mCellWidth, y, mPaint); + } + } + } + } +}