Contact.java

  1package eu.siacs.conversations.entities;
  2
  3import android.content.ContentValues;
  4import android.database.Cursor;
  5
  6import org.json.JSONArray;
  7import org.json.JSONException;
  8import org.json.JSONObject;
  9
 10import java.util.ArrayList;
 11import java.util.List;
 12import java.util.Locale;
 13
 14import eu.siacs.conversations.Config;
 15import eu.siacs.conversations.utils.UIHelper;
 16import eu.siacs.conversations.xml.Element;
 17import eu.siacs.conversations.xmpp.jid.InvalidJidException;
 18import eu.siacs.conversations.xmpp.jid.Jid;
 19import eu.siacs.conversations.xmpp.pep.Avatar;
 20
 21public class Contact implements ListItem, Blockable {
 22	public static final String TABLENAME = "contacts";
 23
 24	public static final String SYSTEMNAME = "systemname";
 25	public static final String SERVERNAME = "servername";
 26	public static final String JID = "jid";
 27	public static final String OPTIONS = "options";
 28	public static final String SYSTEMACCOUNT = "systemaccount";
 29	public static final String PHOTOURI = "photouri";
 30	public static final String KEYS = "pgpkey";
 31	public static final String ACCOUNT = "accountUuid";
 32	public static final String AVATAR = "avatar";
 33	public static final String LAST_PRESENCE = "last_presence";
 34	public static final String LAST_TIME = "last_time";
 35	public static final String GROUPS = "groups";
 36	public Lastseen lastseen = new Lastseen();
 37	protected String accountUuid;
 38	protected String systemName;
 39	protected String serverName;
 40	protected String presenceName;
 41	protected String commonName;
 42	protected Jid jid;
 43	protected int subscription = 0;
 44	protected String systemAccount;
 45	protected String photoUri;
 46	protected JSONObject keys = new JSONObject();
 47	protected JSONArray groups = new JSONArray();
 48	protected final Presences presences = new Presences();
 49	protected Account account;
 50	protected Avatar avatar;
 51
 52	public Contact(final String account, final String systemName, final String serverName,
 53			final Jid jid, final int subscription, final String photoUri,
 54			final String systemAccount, final String keys, final String avatar, final Lastseen lastseen, final String groups) {
 55		this.accountUuid = account;
 56		this.systemName = systemName;
 57		this.serverName = serverName;
 58		this.jid = jid;
 59		this.subscription = subscription;
 60		this.photoUri = photoUri;
 61		this.systemAccount = systemAccount;
 62		try {
 63			this.keys = (keys == null ? new JSONObject("") : new JSONObject(keys));
 64		} catch (JSONException e) {
 65			this.keys = new JSONObject();
 66		}
 67		if (avatar != null) {
 68			this.avatar = new Avatar();
 69			this.avatar.sha1sum = avatar;
 70			this.avatar.origin = Avatar.Origin.VCARD; //always assume worst
 71		}
 72		try {
 73			this.groups = (groups == null ? new JSONArray() : new JSONArray(groups));
 74		} catch (JSONException e) {
 75			this.groups = new JSONArray();
 76		}
 77		this.lastseen = lastseen;
 78	}
 79
 80	public Contact(final Jid jid) {
 81		this.jid = jid;
 82	}
 83
 84	public static Contact fromCursor(final Cursor cursor) {
 85		final Lastseen lastseen = new Lastseen(
 86				cursor.getString(cursor.getColumnIndex(LAST_PRESENCE)),
 87				cursor.getLong(cursor.getColumnIndex(LAST_TIME)));
 88		final Jid jid;
 89		try {
 90			jid = Jid.fromString(cursor.getString(cursor.getColumnIndex(JID)), true);
 91		} catch (final InvalidJidException e) {
 92			// TODO: Borked DB... handle this somehow?
 93			return null;
 94		}
 95		return new Contact(cursor.getString(cursor.getColumnIndex(ACCOUNT)),
 96				cursor.getString(cursor.getColumnIndex(SYSTEMNAME)),
 97				cursor.getString(cursor.getColumnIndex(SERVERNAME)),
 98				jid,
 99				cursor.getInt(cursor.getColumnIndex(OPTIONS)),
100				cursor.getString(cursor.getColumnIndex(PHOTOURI)),
101				cursor.getString(cursor.getColumnIndex(SYSTEMACCOUNT)),
102				cursor.getString(cursor.getColumnIndex(KEYS)),
103				cursor.getString(cursor.getColumnIndex(AVATAR)),
104				lastseen,
105				cursor.getString(cursor.getColumnIndex(GROUPS)));
106	}
107
108	public String getDisplayName() {
109		if (this.commonName != null && Config.X509_VERIFICATION) {
110			return this.commonName;
111		} else if (this.systemName != null) {
112			return this.systemName;
113		} else if (this.serverName != null) {
114			return this.serverName;
115		} else if (this.presenceName != null) {
116			return this.presenceName;
117		} else if (jid.hasLocalpart()) {
118			return jid.getLocalpart();
119		} else {
120			return jid.getDomainpart();
121		}
122	}
123
124	public String getProfilePhoto() {
125		return this.photoUri;
126	}
127
128	public Jid getJid() {
129		return jid;
130	}
131
132	@Override
133	public List<Tag> getTags() {
134		final ArrayList<Tag> tags = new ArrayList<>();
135		for (final String group : getGroups()) {
136			tags.add(new Tag(group, UIHelper.getColorForName(group)));
137		}
138		switch (getMostAvailableStatus()) {
139			case CHAT:
140			case ONLINE:
141				tags.add(new Tag("online", 0xff259b24));
142				break;
143			case AWAY:
144				tags.add(new Tag("away", 0xffff9800));
145				break;
146			case XA:
147				tags.add(new Tag("not available", 0xfff44336));
148				break;
149			case DND:
150				tags.add(new Tag("dnd", 0xfff44336));
151				break;
152		}
153		if (isBlocked()) {
154			tags.add(new Tag("blocked", 0xff2e2f3b));
155		}
156		return tags;
157	}
158
159	public boolean match(String needle) {
160		if (needle == null || needle.isEmpty()) {
161			return true;
162		}
163		needle = needle.toLowerCase(Locale.US).trim();
164		String[] parts = needle.split("\\s+");
165		if (parts.length > 1) {
166			for(int i = 0; i < parts.length; ++i) {
167				if (!match(parts[i])) {
168					return false;
169				}
170			}
171			return true;
172		} else {
173			return jid.toString().contains(needle) ||
174				getDisplayName().toLowerCase(Locale.US).contains(needle) ||
175				matchInTag(needle);
176		}
177	}
178
179	private boolean matchInTag(String needle) {
180		needle = needle.toLowerCase(Locale.US);
181		for (Tag tag : getTags()) {
182			if (tag.getName().toLowerCase(Locale.US).contains(needle)) {
183				return true;
184			}
185		}
186		return false;
187	}
188
189	public ContentValues getContentValues() {
190		synchronized (this.keys) {
191			final ContentValues values = new ContentValues();
192			values.put(ACCOUNT, accountUuid);
193			values.put(SYSTEMNAME, systemName);
194			values.put(SERVERNAME, serverName);
195			values.put(JID, jid.toString());
196			values.put(OPTIONS, subscription);
197			values.put(SYSTEMACCOUNT, systemAccount);
198			values.put(PHOTOURI, photoUri);
199			values.put(KEYS, keys.toString());
200			values.put(AVATAR, avatar == null ? null : avatar.getFilename());
201			values.put(LAST_PRESENCE, lastseen.presence);
202			values.put(LAST_TIME, lastseen.time);
203			values.put(GROUPS, groups.toString());
204			return values;
205		}
206	}
207
208	public int getSubscription() {
209		return this.subscription;
210	}
211
212	public Account getAccount() {
213		return this.account;
214	}
215
216	public void setAccount(Account account) {
217		this.account = account;
218		this.accountUuid = account.getUuid();
219	}
220
221	public Presences getPresences() {
222		return this.presences;
223	}
224
225	public void updatePresence(final String resource, final Presence presence) {
226		this.presences.updatePresence(resource, presence);
227	}
228
229	public void removePresence(final String resource) {
230		this.presences.removePresence(resource);
231	}
232
233	public void clearPresences() {
234		this.presences.clearPresences();
235		this.resetOption(Options.PENDING_SUBSCRIPTION_REQUEST);
236	}
237
238	public Presence.Status getMostAvailableStatus() {
239		Presence p = this.presences.getMostAvailablePresence();
240		if (p == null) {
241			return Presence.Status.OFFLINE;
242		}
243
244		return p.getStatus();
245	}
246
247	public boolean setPhotoUri(String uri) {
248		if (uri != null && !uri.equals(this.photoUri)) {
249			this.photoUri = uri;
250			return true;
251		} else if (this.photoUri != null && uri == null) {
252			this.photoUri = null;
253			return true;
254		} else {
255			return false;
256		}
257	}
258
259	public void setServerName(String serverName) {
260		this.serverName = serverName;
261	}
262
263	public void setSystemName(String systemName) {
264		this.systemName = systemName;
265	}
266
267	public void setPresenceName(String presenceName) {
268		this.presenceName = presenceName;
269	}
270
271	public String getSystemAccount() {
272		return systemAccount;
273	}
274
275	public void setSystemAccount(String account) {
276		this.systemAccount = account;
277	}
278
279	public List<String> getGroups() {
280		ArrayList<String> groups = new ArrayList<String>();
281		for (int i = 0; i < this.groups.length(); ++i) {
282			try {
283				groups.add(this.groups.getString(i));
284			} catch (final JSONException ignored) {
285			}
286		}
287		return groups;
288	}
289
290	public ArrayList<String> getOtrFingerprints() {
291		synchronized (this.keys) {
292			final ArrayList<String> fingerprints = new ArrayList<String>();
293			try {
294				if (this.keys.has("otr_fingerprints")) {
295					final JSONArray prints = this.keys.getJSONArray("otr_fingerprints");
296					for (int i = 0; i < prints.length(); ++i) {
297						final String print = prints.isNull(i) ? null : prints.getString(i);
298						if (print != null && !print.isEmpty()) {
299							fingerprints.add(prints.getString(i));
300						}
301					}
302				}
303			} catch (final JSONException ignored) {
304
305			}
306			return fingerprints;
307		}
308	}
309	public boolean addOtrFingerprint(String print) {
310		synchronized (this.keys) {
311			if (getOtrFingerprints().contains(print)) {
312				return false;
313			}
314			try {
315				JSONArray fingerprints;
316				if (!this.keys.has("otr_fingerprints")) {
317					fingerprints = new JSONArray();
318				} else {
319					fingerprints = this.keys.getJSONArray("otr_fingerprints");
320				}
321				fingerprints.put(print);
322				this.keys.put("otr_fingerprints", fingerprints);
323				return true;
324			} catch (final JSONException ignored) {
325				return false;
326			}
327		}
328	}
329
330	public long getPgpKeyId() {
331		synchronized (this.keys) {
332			if (this.keys.has("pgp_keyid")) {
333				try {
334					return this.keys.getLong("pgp_keyid");
335				} catch (JSONException e) {
336					return 0;
337				}
338			} else {
339				return 0;
340			}
341		}
342	}
343
344	public void setPgpKeyId(long keyId) {
345		synchronized (this.keys) {
346			try {
347				this.keys.put("pgp_keyid", keyId);
348			} catch (final JSONException ignored) {
349			}
350		}
351	}
352
353	public void setOption(int option) {
354		this.subscription |= 1 << option;
355	}
356
357	public void resetOption(int option) {
358		this.subscription &= ~(1 << option);
359	}
360
361	public boolean getOption(int option) {
362		return ((this.subscription & (1 << option)) != 0);
363	}
364
365	public boolean showInRoster() {
366		return (this.getOption(Contact.Options.IN_ROSTER) && (!this
367					.getOption(Contact.Options.DIRTY_DELETE)))
368			|| (this.getOption(Contact.Options.DIRTY_PUSH));
369	}
370
371	public void parseSubscriptionFromElement(Element item) {
372		String ask = item.getAttribute("ask");
373		String subscription = item.getAttribute("subscription");
374
375		if (subscription != null) {
376			switch (subscription) {
377				case "to":
378					this.resetOption(Options.FROM);
379					this.setOption(Options.TO);
380					break;
381				case "from":
382					this.resetOption(Options.TO);
383					this.setOption(Options.FROM);
384					this.resetOption(Options.PREEMPTIVE_GRANT);
385					break;
386				case "both":
387					this.setOption(Options.TO);
388					this.setOption(Options.FROM);
389					this.resetOption(Options.PREEMPTIVE_GRANT);
390					break;
391				case "none":
392					this.resetOption(Options.FROM);
393					this.resetOption(Options.TO);
394					break;
395			}
396		}
397
398		// do NOT override asking if pending push request
399		if (!this.getOption(Contact.Options.DIRTY_PUSH)) {
400			if ((ask != null) && (ask.equals("subscribe"))) {
401				this.setOption(Contact.Options.ASKING);
402			} else {
403				this.resetOption(Contact.Options.ASKING);
404			}
405		}
406	}
407
408	public void parseGroupsFromElement(Element item) {
409		this.groups = new JSONArray();
410		for (Element element : item.getChildren()) {
411			if (element.getName().equals("group") && element.getContent() != null) {
412				this.groups.put(element.getContent());
413			}
414		}
415	}
416
417	public Element asElement() {
418		final Element item = new Element("item");
419		item.setAttribute("jid", this.jid.toString());
420		if (this.serverName != null) {
421			item.setAttribute("name", this.serverName);
422		}
423		for (String group : getGroups()) {
424			item.addChild("group").setContent(group);
425		}
426		return item;
427	}
428
429	@Override
430	public int compareTo(final ListItem another) {
431		return this.getDisplayName().compareToIgnoreCase(
432				another.getDisplayName());
433	}
434
435	public Jid getServer() {
436		return getJid().toDomainJid();
437	}
438
439	public boolean setAvatar(Avatar avatar) {
440		if (this.avatar != null && this.avatar.equals(avatar)) {
441			return false;
442		} else {
443			if (this.avatar != null && this.avatar.origin == Avatar.Origin.PEP && avatar.origin == Avatar.Origin.VCARD) {
444				return false;
445			}
446			this.avatar = avatar;
447			return true;
448		}
449	}
450
451	public String getAvatar() {
452		return avatar == null ? null : avatar.getFilename();
453	}
454
455	public boolean deleteOtrFingerprint(String fingerprint) {
456		synchronized (this.keys) {
457			boolean success = false;
458			try {
459				if (this.keys.has("otr_fingerprints")) {
460					JSONArray newPrints = new JSONArray();
461					JSONArray oldPrints = this.keys
462							.getJSONArray("otr_fingerprints");
463					for (int i = 0; i < oldPrints.length(); ++i) {
464						if (!oldPrints.getString(i).equals(fingerprint)) {
465							newPrints.put(oldPrints.getString(i));
466						} else {
467							success = true;
468						}
469					}
470					this.keys.put("otr_fingerprints", newPrints);
471				}
472				return success;
473			} catch (JSONException e) {
474				return false;
475			}
476		}
477	}
478
479	public boolean trusted() {
480		return getOption(Options.FROM) && getOption(Options.TO);
481	}
482
483	public String getShareableUri() {
484		if (getOtrFingerprints().size() >= 1) {
485			String otr = getOtrFingerprints().get(0);
486			return "xmpp:" + getJid().toBareJid().toString() + "?otr-fingerprint=" + otr;
487		} else {
488			return "xmpp:" + getJid().toBareJid().toString();
489		}
490	}
491
492	@Override
493	public boolean isBlocked() {
494		return getAccount().isBlocked(this);
495	}
496
497	@Override
498	public boolean isDomainBlocked() {
499		return getAccount().isBlocked(this.getJid().toDomainJid());
500	}
501
502	@Override
503	public Jid getBlockedJid() {
504		if (isDomainBlocked()) {
505			return getJid().toDomainJid();
506		} else {
507			return getJid();
508		}
509	}
510
511	public boolean isSelf() {
512		return account.getJid().toBareJid().equals(getJid().toBareJid());
513	}
514
515	public void setCommonName(String cn) {
516		this.commonName = cn;
517	}
518
519	public static class Lastseen {
520		public long time;
521		public String presence;
522
523		public Lastseen() {
524			this(null, 0);
525		}
526
527		public Lastseen(final String presence, final long time) {
528			this.presence = presence;
529			this.time = time;
530		}
531	}
532
533	public final class Options {
534		public static final int TO = 0;
535		public static final int FROM = 1;
536		public static final int ASKING = 2;
537		public static final int PREEMPTIVE_GRANT = 3;
538		public static final int IN_ROSTER = 4;
539		public static final int PENDING_SUBSCRIPTION_REQUEST = 5;
540		public static final int DIRTY_PUSH = 6;
541		public static final int DIRTY_DELETE = 7;
542	}
543}