show identity type for device selection

Daniel Gultsch created

Change summary

src/main/java/eu/siacs/conversations/entities/Contact.java               |   6 
src/main/java/eu/siacs/conversations/entities/Message.java               |   6 
src/main/java/eu/siacs/conversations/entities/Presences.java             |  29 
src/main/java/eu/siacs/conversations/parser/AbstractParser.java          |   2 
src/main/java/eu/siacs/conversations/persistance/FileBackend.java        |   7 
src/main/java/eu/siacs/conversations/services/XmppConnectionService.java |   2 
src/main/java/eu/siacs/conversations/ui/XmppActivity.java                | 101 
src/main/java/eu/siacs/conversations/utils/CryptoHelper.java             |   3 
src/main/java/eu/siacs/conversations/utils/UIHelper.java                 |  18 
src/main/res/values/strings.xml                                          |   7 
10 files changed, 135 insertions(+), 46 deletions(-)

Detailed changes

src/main/java/eu/siacs/conversations/entities/Contact.java 🔗

@@ -526,11 +526,11 @@ public class Contact implements ListItem, Blockable {
 		return this.mLastseen;
 	}
 
-	public void setLastPresence(String presence) {
-		this.mLastPresence = presence;
+	public void setLastResource(String resource) {
+		this.mLastPresence = resource;
 	}
 
-	public String getLastPresence() {
+	public String getLastResource() {
 		return this.mLastPresence;
 	}
 

src/main/java/eu/siacs/conversations/entities/Message.java 🔗

@@ -5,10 +5,10 @@ import android.database.Cursor;
 
 import java.net.MalformedURLException;
 import java.net.URL;
-import java.util.Arrays;
 
 import eu.siacs.conversations.Config;
 import eu.siacs.conversations.crypto.axolotl.XmppAxolotlSession;
+import eu.siacs.conversations.utils.CryptoHelper;
 import eu.siacs.conversations.utils.GeoHelper;
 import eu.siacs.conversations.utils.MimeUtils;
 import eu.siacs.conversations.utils.UIHelper;
@@ -396,7 +396,7 @@ public class Message extends AbstractEntity {
 						&& this.counterpart.equals(message.getCounterpart())
 						&& (body.equals(otherBody)
 						||(message.getEncryption() == Message.ENCRYPTION_PGP
-						&&  message.getRemoteMsgId().matches("[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}"))) ;
+						&& CryptoHelper.UUID_PATTERN.matcher(message.getRemoteMsgId()).matches()));
 			} else {
 				return this.remoteMsgId == null
 						&& this.counterpart.equals(message.getCounterpart())
@@ -550,7 +550,7 @@ public class Message extends AbstractEntity {
 			try {
 				counterpart = Jid.fromParts(conversation.getJid().getLocalpart(),
 						conversation.getJid().getDomainpart(),
-						presences.asStringArray()[0]);
+						presences.toResourceArray()[0]);
 				return true;
 			} catch (InvalidJidException e) {
 				counterpart = null;

src/main/java/eu/siacs/conversations/entities/Presences.java 🔗

@@ -1,7 +1,10 @@
 package eu.siacs.conversations.entities;
 
+import android.util.Pair;
+
 import java.util.ArrayList;
 import java.util.Collections;
+import java.util.HashMap;
 import java.util.Hashtable;
 import java.util.Iterator;
 import java.util.List;
@@ -54,7 +57,7 @@ public class Presences {
 		}
 	}
 
-	public String[] asStringArray() {
+	public String[] toResourceArray() {
 		synchronized (this.presences) {
 			final String[] presencesArray = new String[presences.size()];
 			presences.keySet().toArray(presencesArray);
@@ -104,4 +107,28 @@ public class Presences {
 		}
 		return true;
 	}
+
+	public Pair<Map<String, String>,Map<String,String>> toTypeAndNameMap() {
+		Map<String,String> typeMap = new HashMap<>();
+		Map<String,String> nameMap = new HashMap<>();
+		synchronized (this.presences) {
+			for(Map.Entry<String,Presence> presenceEntry : this.presences.entrySet()) {
+				String resource = presenceEntry.getKey();
+				Presence presence = presenceEntry.getValue();
+				ServiceDiscoveryResult serviceDiscoveryResult = presence == null ? null : presence.getServiceDiscoveryResult();
+				if (serviceDiscoveryResult != null && serviceDiscoveryResult.getIdentities().size() > 0) {
+					ServiceDiscoveryResult.Identity identity = serviceDiscoveryResult.getIdentities().get(0);
+					String type = identity.getType();
+					String name = identity.getName();
+					if (type != null) {
+						typeMap.put(resource,type);
+					}
+					if (name != null) {
+						nameMap.put(resource, name);
+					}
+				}
+			}
+		}
+		return new Pair(typeMap,nameMap);
+	}
 }

src/main/java/eu/siacs/conversations/parser/AbstractParser.java 🔗

@@ -62,7 +62,7 @@ public abstract class AbstractParser {
 
 	protected void updateLastseen(final Account account, final Jid from) {
 		final Contact contact = account.getRoster().getContact(from);
-		contact.setLastPresence(from.isBareJid() ? "" : from.getResourcepart());
+		contact.setLastResource(from.isBareJid() ? "" : from.getResourcepart());
 	}
 
 	protected String avatarData(Element items) {

src/main/java/eu/siacs/conversations/persistance/FileBackend.java 🔗

@@ -401,7 +401,12 @@ public class FileBackend {
 	private Bitmap getFullsizeImagePreview(File file, int size) {
 		BitmapFactory.Options options = new BitmapFactory.Options();
 		options.inSampleSize = calcSampleSize(file, size);
-		return BitmapFactory.decodeFile(file.getAbsolutePath(), options);
+		try {
+			return BitmapFactory.decodeFile(file.getAbsolutePath(), options);
+		} catch (OutOfMemoryError e) {
+			options.inSampleSize *= 2;
+			return BitmapFactory.decodeFile(file.getAbsolutePath(), options);
+		}
 	}
 
 	private Bitmap getVideoPreview(File file, int size) {

src/main/java/eu/siacs/conversations/services/XmppConnectionService.java 🔗

@@ -196,7 +196,7 @@ public class XmppConnectionService extends Service {
 					if (contact.getPresences().size() >= 1) {
 						if (conversation.hasValidOtrSession()) {
 							String otrResource = conversation.getOtrSession().getSessionID().getUserID();
-							if (!(Arrays.asList(contact.getPresences().asStringArray()).contains(otrResource))) {
+							if (!(Arrays.asList(contact.getPresences().toResourceArray()).contains(otrResource))) {
 								conversation.endOtrIfNeeded();
 							}
 						}

src/main/java/eu/siacs/conversations/ui/XmppActivity.java 🔗

@@ -44,6 +44,7 @@ import android.preference.PreferenceManager;
 import android.text.InputType;
 import android.util.DisplayMetrics;
 import android.util.Log;
+import android.util.Pair;
 import android.view.MenuItem;
 import android.view.View;
 import android.view.inputmethod.InputMethodManager;
@@ -66,9 +67,13 @@ import net.java.otr4j.session.SessionID;
 import java.io.FileNotFoundException;
 import java.lang.ref.WeakReference;
 import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashMap;
 import java.util.Hashtable;
 import java.util.List;
+import java.util.Map;
 import java.util.concurrent.RejectedExecutionException;
+import java.util.concurrent.atomic.AtomicInteger;
 
 import eu.siacs.conversations.Config;
 import eu.siacs.conversations.R;
@@ -78,13 +83,16 @@ import eu.siacs.conversations.entities.Contact;
 import eu.siacs.conversations.entities.Conversation;
 import eu.siacs.conversations.entities.Message;
 import eu.siacs.conversations.entities.MucOptions;
+import eu.siacs.conversations.entities.Presence;
 import eu.siacs.conversations.entities.Presences;
+import eu.siacs.conversations.entities.ServiceDiscoveryResult;
 import eu.siacs.conversations.services.AvatarService;
 import eu.siacs.conversations.services.XmppConnectionService;
 import eu.siacs.conversations.services.XmppConnectionService.XmppConnectionBinder;
 import eu.siacs.conversations.ui.widget.Switch;
 import eu.siacs.conversations.utils.CryptoHelper;
 import eu.siacs.conversations.utils.ExceptionHelper;
+import eu.siacs.conversations.utils.UIHelper;
 import eu.siacs.conversations.xmpp.OnKeyStatusUpdated;
 import eu.siacs.conversations.xmpp.OnUpdateBlocklist;
 import eu.siacs.conversations.xmpp.jid.InvalidJidException;
@@ -940,7 +948,7 @@ public abstract class XmppActivity extends Activity {
 		} else 	if (!contact.showInRoster()) {
 			showAddToRosterDialog(conversation);
 		} else {
-			Presences presences = contact.getPresences();
+			final Presences presences = contact.getPresences();
 			if (presences.size() == 0) {
 				if (!contact.getOption(Contact.Options.TO)
 						&& !contact.getOption(Contact.Options.ASKING)
@@ -954,7 +962,7 @@ public abstract class XmppActivity extends Activity {
 					listener.onPresenceSelected();
 				}
 			} else if (presences.size() == 1) {
-				String presence = presences.asStringArray()[0];
+				String presence = presences.toResourceArray()[0];
 				try {
 					conversation.setNextCounterpart(Jid.fromParts(contact.getJid().getLocalpart(),contact.getJid().getDomainpart(),presence));
 				} catch (InvalidJidException e) {
@@ -962,49 +970,72 @@ public abstract class XmppActivity extends Activity {
 				}
 				listener.onPresenceSelected();
 			} else {
-				final StringBuilder presence = new StringBuilder();
-				AlertDialog.Builder builder = new AlertDialog.Builder(this);
-				builder.setTitle(getString(R.string.choose_presence));
-				final String[] presencesArray = presences.asStringArray();
-				int preselectedPresence = 0;
-				for (int i = 0; i < presencesArray.length; ++i) {
-					if (presencesArray[i].equals(contact.getLastPresence())) {
-						preselectedPresence = i;
-						break;
+				showPresenceSelectionDialog(presences,conversation,listener);
+			}
+		}
+	}
+
+	private void showPresenceSelectionDialog(Presences presences, final Conversation conversation, final OnPresenceSelected listener) {
+		final Contact contact = conversation.getContact();
+		AlertDialog.Builder builder = new AlertDialog.Builder(this);
+		builder.setTitle(getString(R.string.choose_presence));
+		final String[] resourceArray = presences.toResourceArray();
+		Pair<Map<String, String>, Map<String, String>> typeAndName = presences.toTypeAndNameMap();
+		final Map<String,String> resourceTypeMap = typeAndName.first;
+		final Map<String,String> resourceNameMap = typeAndName.second;
+		final String[] readableIdentities = new String[resourceArray.length];
+		final AtomicInteger selectedResource = new AtomicInteger(0);
+		for (int i = 0; i < resourceArray.length; ++i) {
+			String resource = resourceArray[i];
+			if (resource.equals(contact.getLastResource())) {
+				selectedResource.set(i);
+			}
+			String type = resourceTypeMap.get(resource);
+			String name = resourceNameMap.get(resource);
+			if (type != null) {
+				if (Collections.frequency(resourceTypeMap.values(),type) == 1) {
+					readableIdentities[i] = UIHelper.tranlasteType(this,type);
+				} else if (name != null) {
+					if (Collections.frequency(resourceNameMap.values(), name) == 1
+							|| CryptoHelper.UUID_PATTERN.matcher(resource).matches()) {
+						readableIdentities[i] = UIHelper.tranlasteType(this,type) + "  (" + name+")";
+					} else {
+						readableIdentities[i] = UIHelper.tranlasteType(this,type) + " (" + name +" / " + resource+")";
 					}
+				} else {
+					readableIdentities[i] = UIHelper.tranlasteType(this,type) + " (" + resource+")";
 				}
-				presence.append(presencesArray[preselectedPresence]);
-				builder.setSingleChoiceItems(presencesArray,
-						preselectedPresence,
-						new DialogInterface.OnClickListener() {
-
-							@Override
-							public void onClick(DialogInterface dialog,
-									int which) {
-								presence.delete(0, presence.length());
-								presence.append(presencesArray[which]);
-							}
-						});
-				builder.setNegativeButton(R.string.cancel, null);
-				builder.setPositiveButton(R.string.ok, new OnClickListener() {
+			} else {
+				readableIdentities[i] = resource;
+			}
+		}
+		builder.setSingleChoiceItems(readableIdentities,
+				selectedResource.get(),
+				new DialogInterface.OnClickListener() {
 
 					@Override
 					public void onClick(DialogInterface dialog, int which) {
-						try {
-							conversation.setNextCounterpart(Jid.fromParts(contact.getJid().getLocalpart(),contact.getJid().getDomainpart(),presence.toString()));
-						} catch (InvalidJidException e) {
-							conversation.setNextCounterpart(null);
-						}
-						listener.onPresenceSelected();
+						selectedResource.set(which);
 					}
 				});
-				builder.create().show();
+		builder.setNegativeButton(R.string.cancel, null);
+		builder.setPositiveButton(R.string.ok, new OnClickListener() {
+
+			@Override
+			public void onClick(DialogInterface dialog, int which) {
+				try {
+					Jid next = Jid.fromParts(contact.getJid().getLocalpart(),contact.getJid().getDomainpart(),resourceArray[selectedResource.get()]);
+					conversation.setNextCounterpart(next);
+				} catch (InvalidJidException e) {
+					conversation.setNextCounterpart(null);
+				}
+				listener.onPresenceSelected();
 			}
-		}
+		});
+		builder.create().show();
 	}
 
-	protected void onActivityResult(int requestCode, int resultCode,
-			final Intent data) {
+	protected void onActivityResult(int requestCode, int resultCode, final Intent data) {
 		super.onActivityResult(requestCode, resultCode, data);
 		if (requestCode == REQUEST_INVITE_TO_CONVERSATION && resultCode == RESULT_OK) {
 			mPendingConferenceInvite = ConferenceInvite.parse(data);

src/main/java/eu/siacs/conversations/utils/CryptoHelper.java 🔗

@@ -21,6 +21,7 @@ import java.util.Iterator;
 import java.util.LinkedHashSet;
 import java.util.List;
 import java.util.Locale;
+import java.util.regex.Pattern;
 
 import eu.siacs.conversations.Config;
 import eu.siacs.conversations.R;
@@ -31,6 +32,8 @@ import eu.siacs.conversations.xmpp.jid.Jid;
 public final class CryptoHelper {
 	public static final String FILETRANSFER = "?FILETRANSFERv1:";
 	private final static char[] hexArray = "0123456789abcdef".toCharArray();
+
+	public static final Pattern UUID_PATTERN = Pattern.compile("[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}");
 	final public static byte[] ONE = new byte[] { 0, 0, 0, 1 };
 
 	public static String bytesToHex(byte[] bytes) {

src/main/java/eu/siacs/conversations/utils/UIHelper.java 🔗

@@ -18,6 +18,7 @@ import eu.siacs.conversations.entities.ListItem;
 import eu.siacs.conversations.entities.Message;
 import eu.siacs.conversations.entities.Presence;
 import eu.siacs.conversations.entities.Transferable;
+import eu.siacs.conversations.ui.XmppActivity;
 import eu.siacs.conversations.xmpp.jid.Jid;
 
 public class UIHelper {
@@ -286,4 +287,21 @@ public class UIHelper {
 				return new ListItem.Tag(context.getString(R.string.presence_online), 0xff259b24);
 		}
 	}
+
+	public static String tranlasteType(Context context, String type) {
+		switch (type.toLowerCase()) {
+			case "pc":
+				return context.getString(R.string.type_pc);
+			case "phone":
+				return context.getString(R.string.type_phone);
+			case "tablet":
+				return context.getString(R.string.type_tablet);
+			case "web":
+				return context.getString(R.string.type_web);
+			case "console":
+				return context.getString(R.string.type_console);
+			default:
+				return type;
+		}
+	}
 }

src/main/res/values/strings.xml 🔗

@@ -79,7 +79,7 @@
 	<string name="clear_histor_msg">Do you want to delete all messages within this Conversation?\n\n<b>Warning:</b> This will not influence messages stored on other devices or servers.</string>
 	<string name="delete_messages">Delete messages</string>
 	<string name="also_end_conversation">End this conversation afterwards</string>
-	<string name="choose_presence">Choose presence to contact</string>
+	<string name="choose_presence">Choose device</string>
 	<string name="send_unencrypted_message">Send unencrypted message</string>
 	<string name="send_message_to_x">Send message to %s</string>
 	<string name="send_otr_message">Send OTR encrypted message</string>
@@ -669,4 +669,9 @@
 	<string name="pref_use_green_background_summary">Use green background for received messages</string>
 	<string name="unable_to_connect_to_keychain">Unable to connect to OpenKeychain</string>
 	<string name="this_device_is_no_longer_in_use">This device is no longer in use</string>
+	<string name="type_pc">Computer</string>
+	<string name="type_phone">Mobile phone</string>
+	<string name="type_tablet">Tablet</string>
+	<string name="type_web">Web browser</string>
+	<string name="type_console">Console</string>
 </resources>