diff options
Diffstat (limited to 'src')
13 files changed, 219 insertions, 29 deletions
diff --git a/src/main/java/eu/siacs/conversations/crypto/axolotl/AxolotlService.java b/src/main/java/eu/siacs/conversations/crypto/axolotl/AxolotlService.java index ab3aefac..e15a73ba 100644 --- a/src/main/java/eu/siacs/conversations/crypto/axolotl/AxolotlService.java +++ b/src/main/java/eu/siacs/conversations/crypto/axolotl/AxolotlService.java @@ -515,6 +515,7 @@ public class AxolotlService implements OnAdvancedStreamFeaturesLoaded { if (changed) { if (account.getPrivateKeyAlias() != null && Config.X509_VERIFICATION) { + mXmppConnectionService.publishDisplayName(account); publishDeviceVerificationAndBundle(signedPreKeyRecord, preKeyRecords, announce, wipe); } else { publishDeviceBundle(signedPreKeyRecord, preKeyRecords, announce, wipe); diff --git a/src/main/java/eu/siacs/conversations/entities/Account.java b/src/main/java/eu/siacs/conversations/entities/Account.java index 06f2264b..fbee5b8a 100644 --- a/src/main/java/eu/siacs/conversations/entities/Account.java +++ b/src/main/java/eu/siacs/conversations/entities/Account.java @@ -38,6 +38,7 @@ public class Account extends AbstractEntity { public static final String ROSTERVERSION = "rosterversion"; public static final String KEYS = "keys"; public static final String AVATAR = "avatar"; + public static final String DISPLAY_NAME = "display_name"; public static final String PINNED_MECHANISM_KEY = "pinned_mechanism"; @@ -50,6 +51,14 @@ public class Account extends AbstractEntity { return xmppConnection != null && xmppConnection.getFeatures().httpUpload(); } + public void setDisplayName(String displayName) { + this.displayName = displayName; + } + + public String getDisplayName() { + return displayName; + } + public static enum State { DISABLED, OFFLINE, @@ -125,6 +134,7 @@ public class Account extends AbstractEntity { protected State status = State.OFFLINE; protected JSONObject keys = new JSONObject(); protected String avatar; + protected String displayName = null; protected boolean online = false; private OtrService mOtrService = null; private AxolotlService axolotlService = null; @@ -142,12 +152,12 @@ public class Account extends AbstractEntity { public Account(final Jid jid, final String password) { this(java.util.UUID.randomUUID().toString(), jid, - password, 0, null, "", null); + password, 0, null, "", null, null); } public Account(final String uuid, final Jid jid, final String password, final int options, final String rosterVersion, final String keys, - final String avatar) { + final String avatar, String displayName) { this.uuid = uuid; this.jid = jid; if (jid.isBareJid()) { @@ -162,6 +172,7 @@ public class Account extends AbstractEntity { this.keys = new JSONObject(); } this.avatar = avatar; + this.displayName = displayName; } public static Account fromCursor(final Cursor cursor) { @@ -177,7 +188,8 @@ public class Account extends AbstractEntity { cursor.getInt(cursor.getColumnIndex(OPTIONS)), cursor.getString(cursor.getColumnIndex(ROSTERVERSION)), cursor.getString(cursor.getColumnIndex(KEYS)), - cursor.getString(cursor.getColumnIndex(AVATAR))); + cursor.getString(cursor.getColumnIndex(AVATAR)), + cursor.getString(cursor.getColumnIndex(DISPLAY_NAME))); } public boolean isOptionSet(final int option) { @@ -289,6 +301,7 @@ public class Account extends AbstractEntity { values.put(KEYS, this.keys.toString()); values.put(ROSTERVERSION, rosterVersion); values.put(AVATAR, avatar); + values.put(DISPLAY_NAME, displayName); return values; } diff --git a/src/main/java/eu/siacs/conversations/generator/AbstractGenerator.java b/src/main/java/eu/siacs/conversations/generator/AbstractGenerator.java index 879a5680..5741af53 100644 --- a/src/main/java/eu/siacs/conversations/generator/AbstractGenerator.java +++ b/src/main/java/eu/siacs/conversations/generator/AbstractGenerator.java @@ -27,6 +27,7 @@ public abstract class AbstractGenerator { "http://jabber.org/protocol/caps", "http://jabber.org/protocol/disco#info", "urn:xmpp:avatar:metadata+notify", + "http://jabber.org/protocol/nick+notify", "urn:xmpp:ping", "jabber:iq:version", "http://jabber.org/protocol/chatstates", diff --git a/src/main/java/eu/siacs/conversations/generator/IqGenerator.java b/src/main/java/eu/siacs/conversations/generator/IqGenerator.java index 7457cad8..345f68ae 100644 --- a/src/main/java/eu/siacs/conversations/generator/IqGenerator.java +++ b/src/main/java/eu/siacs/conversations/generator/IqGenerator.java @@ -82,6 +82,12 @@ public class IqGenerator extends AbstractGenerator { return packet; } + public IqPacket publishNick(String nick) { + final Element item = new Element("item"); + item.addChild("nick","http://jabber.org/protocol/nick").setContent(nick); + return publish("http://jabber.org/protocol/nick", item); + } + public IqPacket publishAvatar(Avatar avatar) { final Element item = new Element("item"); item.setAttribute("id", avatar.sha1sum); diff --git a/src/main/java/eu/siacs/conversations/parser/MessageParser.java b/src/main/java/eu/siacs/conversations/parser/MessageParser.java index c534a417..58ca5135 100644 --- a/src/main/java/eu/siacs/conversations/parser/MessageParser.java +++ b/src/main/java/eu/siacs/conversations/parser/MessageParser.java @@ -191,7 +191,7 @@ public class MessageParser extends AbstractParser implements } else if ("http://jabber.org/protocol/nick".equals(node)) { Element i = items.findChild("item"); Element nick = i == null ? null : i.findChild("nick", "http://jabber.org/protocol/nick"); - if (nick != null) { + if (nick != null && nick.getContent() != null) { Contact contact = account.getRoster().getContact(from); contact.setPresenceName(nick.getContent()); mXmppConnectionService.getAvatarService().clear(account); diff --git a/src/main/java/eu/siacs/conversations/persistance/DatabaseBackend.java b/src/main/java/eu/siacs/conversations/persistance/DatabaseBackend.java index 07071323..707237a1 100644 --- a/src/main/java/eu/siacs/conversations/persistance/DatabaseBackend.java +++ b/src/main/java/eu/siacs/conversations/persistance/DatabaseBackend.java @@ -43,7 +43,7 @@ public class DatabaseBackend extends SQLiteOpenHelper { private static DatabaseBackend instance = null; private static final String DATABASE_NAME = "history"; - private static final int DATABASE_VERSION = 18; + private static final int DATABASE_VERSION = 19; private static String CREATE_CONTATCS_STATEMENT = "create table " + Contact.TABLENAME + "(" + Contact.ACCOUNT + " TEXT, " @@ -121,6 +121,7 @@ public class DatabaseBackend extends SQLiteOpenHelper { db.execSQL("create table " + Account.TABLENAME + "(" + Account.UUID + " TEXT PRIMARY KEY," + Account.USERNAME + " TEXT," + Account.SERVER + " TEXT," + Account.PASSWORD + " TEXT," + + Account.DISPLAY_NAME + " TEXT, " + Account.ROSTERVERSION + " TEXT," + Account.OPTIONS + " NUMBER, " + Account.AVATAR + " TEXT, " + Account.KEYS + " TEXT)"); @@ -324,6 +325,9 @@ public class DatabaseBackend extends SQLiteOpenHelper { if (oldVersion < 18 && newVersion >= 18) { db.execSQL("ALTER TABLE " + Message.TABLENAME + " ADD COLUMN "+ Message.READ+ " NUMBER DEFAULT 1"); } + if (oldVersion < 19 && newVersion >= 19) { + db.execSQL("ALTER TABLE " + Account.TABLENAME + " ADD COLUMN "+ Account.DISPLAY_NAME+ " TEXT"); + } } public static synchronized DatabaseBackend getInstance(Context context) { diff --git a/src/main/java/eu/siacs/conversations/services/AvatarService.java b/src/main/java/eu/siacs/conversations/services/AvatarService.java index 7412eb93..3cd32d79 100644 --- a/src/main/java/eu/siacs/conversations/services/AvatarService.java +++ b/src/main/java/eu/siacs/conversations/services/AvatarService.java @@ -16,6 +16,7 @@ import eu.siacs.conversations.entities.Bookmark; import eu.siacs.conversations.entities.Contact; import eu.siacs.conversations.entities.Conversation; import eu.siacs.conversations.entities.ListItem; +import eu.siacs.conversations.entities.Message; import eu.siacs.conversations.entities.MucOptions; import eu.siacs.conversations.utils.UIHelper; @@ -179,9 +180,13 @@ public class AvatarService { } public Bitmap get(Account account, int size) { + return get(account, size, false); + } + + public Bitmap get(Account account, int size, boolean cachedOnly) { final String KEY = key(account, size); Bitmap avatar = mXmppConnectionService.getBitmapCache().get(KEY); - if (avatar != null) { + if (avatar != null || cachedOnly) { return avatar; } avatar = mXmppConnectionService.getFileBackend().getAvatar( @@ -193,6 +198,19 @@ public class AvatarService { return avatar; } + public Bitmap get(Message message, int size, boolean cachedOnly) { + if (message.getStatus() == Message.STATUS_RECEIVED) { + Contact contact = message.getContact(); + if (contact != null) { + return get(contact, size, cachedOnly); + } else { + return get(UIHelper.getMessageDisplayName(message), size, cachedOnly); + } + } else { + return get(message.getConversation().getAccount(), size, cachedOnly); + } + } + public void clear(Account account) { synchronized (this.sizes) { for (Integer size : sizes) { @@ -291,5 +309,4 @@ public class AvatarService { Rect dst = new Rect(dstleft, dsttop, dstright, dstbottom); canvas.drawBitmap(bm, null, dst, null); } - } diff --git a/src/main/java/eu/siacs/conversations/services/XmppConnectionService.java b/src/main/java/eu/siacs/conversations/services/XmppConnectionService.java index 613a5758..9abacbd1 100644 --- a/src/main/java/eu/siacs/conversations/services/XmppConnectionService.java +++ b/src/main/java/eu/siacs/conversations/services/XmppConnectionService.java @@ -1344,6 +1344,7 @@ public class XmppConnectionService extends Service implements OnPhoneContactsLoa Account account = new Account(info.first, ""); account.setPrivateKeyAlias(alias); account.setOption(Account.OPTION_DISABLED, true); + account.setDisplayName(info.second); createAccount(account); callback.onAccountCreated(account); if (Config.X509_VERIFICATION) { @@ -1372,6 +1373,7 @@ public class XmppConnectionService extends Service implements OnPhoneContactsLoa Pair<Jid, String> info = CryptoHelper.extractJidAndName(chain[0]); if (account.getJid().toBareJid().equals(info.first)) { account.setPrivateKeyAlias(alias); + account.setDisplayName(info.second); databaseBackend.updateAccount(account); if (Config.X509_VERIFICATION) { try { @@ -1428,7 +1430,13 @@ public class XmppConnectionService extends Service implements OnPhoneContactsLoa if (account.getXmppConnection() != null) { this.disconnect(account, true); } - databaseBackend.deleteAccount(account); + Runnable runnable = new Runnable() { + @Override + public void run() { + databaseBackend.deleteAccount(account); + } + }; + mDatabaseExecutor.execute(runnable); this.accounts.remove(account); updateAccountUi(); getNotificationService().updateErrorNotification(); @@ -2628,8 +2636,17 @@ public class XmppConnectionService extends Service implements OnPhoneContactsLoa public void markRead(final Conversation conversation) { mNotificationService.clear(conversation); - for (Message message : conversation.markRead()) { - databaseBackend.updateMessage(message); + final List<Message> readMessages = conversation.markRead(); + if (readMessages.size() > 0) { + Runnable runnable = new Runnable() { + @Override + public void run() { + for (Message message : readMessages) { + databaseBackend.updateMessage(message); + } + } + }; + mDatabaseExecutor.execute(runnable); } updateUnreadCountBadge(); } @@ -2845,12 +2862,13 @@ public class XmppConnectionService extends Service implements OnPhoneContactsLoa conversation.clearMessages(); conversation.setHasMessagesLeftOnServer(false); //avoid messages getting loaded through mam conversation.resetLastMessageTransmitted(); - new Thread(new Runnable() { + Runnable runnable = new Runnable() { @Override public void run() { databaseBackend.deleteMessagesInConversation(conversation); } - }).start(); + }; + mDatabaseExecutor.execute(runnable); } public void sendBlockRequest(final Blockable blockable) { @@ -2884,6 +2902,21 @@ public class XmppConnectionService extends Service implements OnPhoneContactsLoa } } + public void publishDisplayName(Account account) { + String displayName = account.getDisplayName(); + if (displayName != null && !displayName.isEmpty()) { + IqPacket publish = mIqGenerator.publishNick(displayName); + sendIqPacket(account, publish, new OnIqPacketReceived() { + @Override + public void onIqPacketReceived(Account account, IqPacket packet) { + if (packet.getType() == IqPacket.TYPE.ERROR) { + Log.d(Config.LOGTAG,account.getJid().toBareJid()+": could not publish nick"); + } + } + }); + } + } + public interface OnAccountCreated { void onAccountCreated(Account account); diff --git a/src/main/java/eu/siacs/conversations/ui/adapter/MessageAdapter.java b/src/main/java/eu/siacs/conversations/ui/adapter/MessageAdapter.java index ec7ff73d..019b83d3 100644 --- a/src/main/java/eu/siacs/conversations/ui/adapter/MessageAdapter.java +++ b/src/main/java/eu/siacs/conversations/ui/adapter/MessageAdapter.java @@ -4,8 +4,13 @@ import android.content.ActivityNotFoundException; import android.content.Intent; import android.content.pm.PackageManager; import android.content.pm.ResolveInfo; +import android.content.res.Resources; +import android.graphics.Bitmap; import android.graphics.Typeface; +import android.graphics.drawable.BitmapDrawable; +import android.graphics.drawable.Drawable; import android.net.Uri; +import android.os.AsyncTask; import android.text.Spannable; import android.text.SpannableString; import android.text.Spanned; @@ -24,7 +29,9 @@ import android.widget.LinearLayout; import android.widget.TextView; import android.widget.Toast; +import java.lang.ref.WeakReference; import java.util.List; +import java.util.concurrent.RejectedExecutionException; import eu.siacs.conversations.R; import eu.siacs.conversations.crypto.axolotl.XmppAxolotlSession; @@ -36,6 +43,7 @@ import eu.siacs.conversations.entities.Message; import eu.siacs.conversations.entities.Message.FileParams; import eu.siacs.conversations.entities.Transferable; import eu.siacs.conversations.ui.ConversationActivity; +import eu.siacs.conversations.utils.CryptoHelper; import eu.siacs.conversations.utils.GeoHelper; import eu.siacs.conversations.utils.UIHelper; @@ -462,6 +470,7 @@ public class MessageAdapter extends ArrayAdapter<Message> { .findViewById(R.id.message_time); viewHolder.indicatorReceived = (ImageView) view .findViewById(R.id.indicator_received); + viewHolder.encryption = (TextView) view.findViewById(R.id.message_encryption); break; case STATUS: view = activity.getLayoutInflater().inflate(R.layout.message_status, parent, false); @@ -491,17 +500,8 @@ public class MessageAdapter extends ArrayAdapter<Message> { viewHolder.status_message.setText(message.getBody()); } return view; - } else if (type == RECEIVED) { - Contact contact = message.getContact(); - if (contact != null) { - viewHolder.contact_picture.setImageBitmap(activity.avatarService().get(contact, activity.getPixel(48))); - } else if (conversation.getMode() == Conversation.MODE_MULTI) { - viewHolder.contact_picture.setImageBitmap(activity.avatarService().get( - UIHelper.getMessageDisplayName(message), - activity.getPixel(48))); - } - } else if (type == SENT) { - viewHolder.contact_picture.setImageBitmap(activity.avatarService().get(account, activity.getPixel(48))); + } else { + loadAvatar(message,viewHolder.contact_picture); } viewHolder.contact_picture @@ -589,8 +589,11 @@ public class MessageAdapter extends ArrayAdapter<Message> { } else { viewHolder.message_box.setBackgroundResource(R.drawable.message_bubble_received); } + viewHolder.encryption.setVisibility(View.GONE); } else { viewHolder.message_box.setBackgroundResource(R.drawable.message_bubble_received_warning); + viewHolder.encryption.setVisibility(View.VISIBLE); + viewHolder.encryption.setText(CryptoHelper.encryptionTypeToText(message.getEncryption())); } } @@ -671,5 +674,89 @@ public class MessageAdapter extends ArrayAdapter<Message> { protected TextView messageBody; protected ImageView contact_picture; protected TextView status_message; + protected TextView encryption; + } + + class BitmapWorkerTask extends AsyncTask<Message, Void, Bitmap> { + private final WeakReference<ImageView> imageViewReference; + private Message message = null; + + public BitmapWorkerTask(ImageView imageView) { + imageViewReference = new WeakReference<>(imageView); + } + + @Override + protected Bitmap doInBackground(Message... params) { + return activity.avatarService().get(params[0], activity.getPixel(48), isCancelled()); + } + + @Override + protected void onPostExecute(Bitmap bitmap) { + if (bitmap != null) { + final ImageView imageView = imageViewReference.get(); + if (imageView != null) { + imageView.setImageBitmap(bitmap); + imageView.setBackgroundColor(0x00000000); + } + } + } + } + + public void loadAvatar(Message message, ImageView imageView) { + if (cancelPotentialWork(message, imageView)) { + final Bitmap bm = activity.avatarService().get(message, activity.getPixel(48), true); + if (bm != null) { + imageView.setImageBitmap(bm); + imageView.setBackgroundColor(0x00000000); + } else { + imageView.setBackgroundColor(UIHelper.getColorForName(UIHelper.getMessageDisplayName(message))); + imageView.setImageDrawable(null); + final BitmapWorkerTask task = new BitmapWorkerTask(imageView); + final AsyncDrawable asyncDrawable = new AsyncDrawable(activity.getResources(), null, task); + imageView.setImageDrawable(asyncDrawable); + try { + task.execute(message); + } catch (final RejectedExecutionException ignored) { + } + } + } + } + + public static boolean cancelPotentialWork(Message message, ImageView imageView) { + final BitmapWorkerTask bitmapWorkerTask = getBitmapWorkerTask(imageView); + + if (bitmapWorkerTask != null) { + final Message oldMessage = bitmapWorkerTask.message; + if (oldMessage == null || message != oldMessage) { + bitmapWorkerTask.cancel(true); + } else { + return false; + } + } + return true; + } + + private static BitmapWorkerTask getBitmapWorkerTask(ImageView imageView) { + if (imageView != null) { + final Drawable drawable = imageView.getDrawable(); + if (drawable instanceof AsyncDrawable) { + final AsyncDrawable asyncDrawable = (AsyncDrawable) drawable; + return asyncDrawable.getBitmapWorkerTask(); + } + } + return null; + } + + static class AsyncDrawable extends BitmapDrawable { + private final WeakReference<BitmapWorkerTask> bitmapWorkerTaskReference; + + public AsyncDrawable(Resources res, Bitmap bitmap, BitmapWorkerTask bitmapWorkerTask) { + super(res, bitmap); + bitmapWorkerTaskReference = new WeakReference<>(bitmapWorkerTask); + } + + public BitmapWorkerTask getBitmapWorkerTask() { + return bitmapWorkerTaskReference.get(); + } } } diff --git a/src/main/java/eu/siacs/conversations/utils/CryptoHelper.java b/src/main/java/eu/siacs/conversations/utils/CryptoHelper.java index 8091a996..ab407249 100644 --- a/src/main/java/eu/siacs/conversations/utils/CryptoHelper.java +++ b/src/main/java/eu/siacs/conversations/utils/CryptoHelper.java @@ -23,6 +23,8 @@ import java.util.LinkedHashSet; import java.util.List; import eu.siacs.conversations.Config; +import eu.siacs.conversations.R; +import eu.siacs.conversations.entities.Message; import eu.siacs.conversations.xmpp.jid.InvalidJidException; import eu.siacs.conversations.xmpp.jid.Jid; @@ -164,4 +166,17 @@ public final class CryptoHelper { return null; } } + + public static int encryptionTypeToText(int encryption) { + switch (encryption) { + case Message.ENCRYPTION_OTR: + return R.string.encryption_choice_otr; + case Message.ENCRYPTION_AXOLOTL: + return R.string.encryption_choice_omemo; + case Message.ENCRYPTION_NONE: + return R.string.encryption_choice_unencrypted; + default: + return R.string.encryption_choice_pgp; + } + } } diff --git a/src/main/java/eu/siacs/conversations/xmpp/XmppConnection.java b/src/main/java/eu/siacs/conversations/xmpp/XmppConnection.java index 1549aeb5..04c0f625 100644 --- a/src/main/java/eu/siacs/conversations/xmpp/XmppConnection.java +++ b/src/main/java/eu/siacs/conversations/xmpp/XmppConnection.java @@ -360,7 +360,8 @@ public class XmppConnection implements Runnable { String.valueOf(saslMechanism.getPriority())); tagReader.reset(); sendStartStream(); - if (tagReader.readTag().isStart("stream")) { + final Tag tag = tagReader.readTag(); + if (tag != null && tag.isStart("stream")) { processStream(); } else { throw new IOException("server didn't restart stream after successful auth"); @@ -647,7 +648,8 @@ public class XmppConnection implements Runnable { sendStartStream(); Log.d(Config.LOGTAG, account.getJid().toBareJid()+ ": TLS connection established"); features.encryptionEnabled = true; - if (tagReader.readTag().isStart("stream")) { + final Tag tag = tagReader.readTag(); + if (tag != null && tag.isStart("stream")) { processStream(); } else { throw new IOException("server didn't restart stream after STARTTLS"); diff --git a/src/main/res/layout/message_received.xml b/src/main/res/layout/message_received.xml index 76df710b..a998bf37 100644 --- a/src/main/res/layout/message_received.xml +++ b/src/main/res/layout/message_received.xml @@ -11,8 +11,8 @@ <com.makeramen.roundedimageview.RoundedImageView android:id="@+id/message_photo" - android:layout_width="wrap_content" - android:layout_height="wrap_content" + android:layout_width="48dp" + android:layout_height="48dp" android:layout_alignParentLeft="true" android:layout_alignParentTop="true" android:scaleType="fitXY" @@ -72,6 +72,17 @@ android:orientation="horizontal" android:paddingBottom="2dp"> + <TextView + android:id="@+id/message_encryption" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_gravity="center_vertical" + android:gravity="center_vertical" + android:layout_marginRight="4sp" + android:textColor="@color/white70" + android:textStyle="bold" + android:textSize="?attr/TextSizeInfo" /> + <ImageView android:id="@+id/security_indicator" android:layout_width="?attr/TextSizeInfo" diff --git a/src/main/res/layout/message_sent.xml b/src/main/res/layout/message_sent.xml index e3b38cd9..55f874e6 100644 --- a/src/main/res/layout/message_sent.xml +++ b/src/main/res/layout/message_sent.xml @@ -11,8 +11,8 @@ <com.makeramen.roundedimageview.RoundedImageView android:id="@+id/message_photo" - android:layout_width="wrap_content" - android:layout_height="wrap_content" + android:layout_width="48dp" + android:layout_height="48dp" android:scaleType="fitXY" android:paddingBottom="3dp" android:src="@drawable/ic_profile" |