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 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 Presences.CHAT:
140			case Presences.ONLINE:
141				tags.add(new Tag("online", 0xff259b24));
142				break;
143			case Presences.AWAY:
144				tags.add(new Tag("away", 0xffff9800));
145				break;
146			case Presences.XA:
147				tags.add(new Tag("not available", 0xfff44336));
148				break;
149			case Presences.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 setPresences(Presences pres) {
226		this.presences = pres;
227	}
228
229	public void updatePresence(final String resource, final int status) {
230		this.presences.updatePresence(resource, status);
231	}
232
233	public void removePresence(final String resource) {
234		this.presences.removePresence(resource);
235	}
236
237	public void clearPresences() {
238		this.presences.clearPresences();
239		this.resetOption(Options.PENDING_SUBSCRIPTION_REQUEST);
240	}
241
242	public int getMostAvailableStatus() {
243		return this.presences.getMostAvailableStatus();
244	}
245
246	public boolean setPhotoUri(String uri) {
247		if (uri != null && !uri.equals(this.photoUri)) {
248			this.photoUri = uri;
249			return true;
250		} else if (this.photoUri != null && uri == null) {
251			this.photoUri = null;
252			return true;
253		} else {
254			return false;
255		}
256	}
257
258	public void setServerName(String serverName) {
259		this.serverName = serverName;
260	}
261
262	public void setSystemName(String systemName) {
263		this.systemName = systemName;
264	}
265
266	public void setPresenceName(String presenceName) {
267		this.presenceName = presenceName;
268	}
269
270	public String getSystemAccount() {
271		return systemAccount;
272	}
273
274	public void setSystemAccount(String account) {
275		this.systemAccount = account;
276	}
277
278	public List<String> getGroups() {
279		ArrayList<String> groups = new ArrayList<String>();
280		for (int i = 0; i < this.groups.length(); ++i) {
281			try {
282				groups.add(this.groups.getString(i));
283			} catch (final JSONException ignored) {
284			}
285		}
286		return groups;
287	}
288
289	public ArrayList<String> getOtrFingerprints() {
290		synchronized (this.keys) {
291			final ArrayList<String> fingerprints = new ArrayList<String>();
292			try {
293				if (this.keys.has("otr_fingerprints")) {
294					final JSONArray prints = this.keys.getJSONArray("otr_fingerprints");
295					for (int i = 0; i < prints.length(); ++i) {
296						final String print = prints.isNull(i) ? null : prints.getString(i);
297						if (print != null && !print.isEmpty()) {
298							fingerprints.add(prints.getString(i));
299						}
300					}
301				}
302			} catch (final JSONException ignored) {
303
304			}
305			return fingerprints;
306		}
307	}
308	public boolean addOtrFingerprint(String print) {
309		synchronized (this.keys) {
310			if (getOtrFingerprints().contains(print)) {
311				return false;
312			}
313			try {
314				JSONArray fingerprints;
315				if (!this.keys.has("otr_fingerprints")) {
316					fingerprints = new JSONArray();
317				} else {
318					fingerprints = this.keys.getJSONArray("otr_fingerprints");
319				}
320				fingerprints.put(print);
321				this.keys.put("otr_fingerprints", fingerprints);
322				return true;
323			} catch (final JSONException ignored) {
324				return false;
325			}
326		}
327	}
328
329	public long getPgpKeyId() {
330		synchronized (this.keys) {
331			if (this.keys.has("pgp_keyid")) {
332				try {
333					return this.keys.getLong("pgp_keyid");
334				} catch (JSONException e) {
335					return 0;
336				}
337			} else {
338				return 0;
339			}
340		}
341	}
342
343	public void setPgpKeyId(long keyId) {
344		synchronized (this.keys) {
345			try {
346				this.keys.put("pgp_keyid", keyId);
347			} catch (final JSONException ignored) {
348			}
349		}
350	}
351
352	public void setOption(int option) {
353		this.subscription |= 1 << option;
354	}
355
356	public void resetOption(int option) {
357		this.subscription &= ~(1 << option);
358	}
359
360	public boolean getOption(int option) {
361		return ((this.subscription & (1 << option)) != 0);
362	}
363
364	public boolean showInRoster() {
365		return (this.getOption(Contact.Options.IN_ROSTER) && (!this
366					.getOption(Contact.Options.DIRTY_DELETE)))
367			|| (this.getOption(Contact.Options.DIRTY_PUSH));
368	}
369
370	public void parseSubscriptionFromElement(Element item) {
371		String ask = item.getAttribute("ask");
372		String subscription = item.getAttribute("subscription");
373
374		if (subscription != null) {
375			switch (subscription) {
376				case "to":
377					this.resetOption(Options.FROM);
378					this.setOption(Options.TO);
379					break;
380				case "from":
381					this.resetOption(Options.TO);
382					this.setOption(Options.FROM);
383					this.resetOption(Options.PREEMPTIVE_GRANT);
384					break;
385				case "both":
386					this.setOption(Options.TO);
387					this.setOption(Options.FROM);
388					this.resetOption(Options.PREEMPTIVE_GRANT);
389					break;
390				case "none":
391					this.resetOption(Options.FROM);
392					this.resetOption(Options.TO);
393					break;
394			}
395		}
396
397		// do NOT override asking if pending push request
398		if (!this.getOption(Contact.Options.DIRTY_PUSH)) {
399			if ((ask != null) && (ask.equals("subscribe"))) {
400				this.setOption(Contact.Options.ASKING);
401			} else {
402				this.resetOption(Contact.Options.ASKING);
403			}
404		}
405	}
406
407	public void parseGroupsFromElement(Element item) {
408		this.groups = new JSONArray();
409		for (Element element : item.getChildren()) {
410			if (element.getName().equals("group") && element.getContent() != null) {
411				this.groups.put(element.getContent());
412			}
413		}
414	}
415
416	public Element asElement() {
417		final Element item = new Element("item");
418		item.setAttribute("jid", this.jid.toString());
419		if (this.serverName != null) {
420			item.setAttribute("name", this.serverName);
421		}
422		for (String group : getGroups()) {
423			item.addChild("group").setContent(group);
424		}
425		return item;
426	}
427
428	@Override
429	public int compareTo(final ListItem another) {
430		return this.getDisplayName().compareToIgnoreCase(
431				another.getDisplayName());
432	}
433
434	public Jid getServer() {
435		return getJid().toDomainJid();
436	}
437
438	public boolean setAvatar(Avatar avatar) {
439		if (this.avatar != null && this.avatar.equals(avatar)) {
440			return false;
441		} else {
442			if (this.avatar != null && this.avatar.origin == Avatar.Origin.PEP && avatar.origin == Avatar.Origin.VCARD) {
443				return false;
444			}
445			this.avatar = avatar;
446			return true;
447		}
448	}
449
450	public String getAvatar() {
451		return avatar == null ? null : avatar.getFilename();
452	}
453
454	public boolean deleteOtrFingerprint(String fingerprint) {
455		synchronized (this.keys) {
456			boolean success = false;
457			try {
458				if (this.keys.has("otr_fingerprints")) {
459					JSONArray newPrints = new JSONArray();
460					JSONArray oldPrints = this.keys
461							.getJSONArray("otr_fingerprints");
462					for (int i = 0; i < oldPrints.length(); ++i) {
463						if (!oldPrints.getString(i).equals(fingerprint)) {
464							newPrints.put(oldPrints.getString(i));
465						} else {
466							success = true;
467						}
468					}
469					this.keys.put("otr_fingerprints", newPrints);
470				}
471				return success;
472			} catch (JSONException e) {
473				return false;
474			}
475		}
476	}
477
478	public boolean trusted() {
479		return getOption(Options.FROM) && getOption(Options.TO);
480	}
481
482	public String getShareableUri() {
483		if (getOtrFingerprints().size() >= 1) {
484			String otr = getOtrFingerprints().get(0);
485			return "xmpp:" + getJid().toBareJid().toString() + "?otr-fingerprint=" + otr;
486		} else {
487			return "xmpp:" + getJid().toBareJid().toString();
488		}
489	}
490
491	@Override
492	public boolean isBlocked() {
493		return getAccount().isBlocked(this);
494	}
495
496	@Override
497	public boolean isDomainBlocked() {
498		return getAccount().isBlocked(this.getJid().toDomainJid());
499	}
500
501	@Override
502	public Jid getBlockedJid() {
503		if (isDomainBlocked()) {
504			return getJid().toDomainJid();
505		} else {
506			return getJid();
507		}
508	}
509
510	public boolean isSelf() {
511		return account.getJid().toBareJid().equals(getJid().toBareJid());
512	}
513
514	public void setCommonName(String cn) {
515		this.commonName = cn;
516	}
517
518	public static class Lastseen {
519		public long time;
520		public String presence;
521
522		public Lastseen() {
523			this(null, 0);
524		}
525
526		public Lastseen(final String presence, final long time) {
527			this.presence = presence;
528			this.time = time;
529		}
530	}
531
532	public final class Options {
533		public static final int TO = 0;
534		public static final int FROM = 1;
535		public static final int ASKING = 2;
536		public static final int PREEMPTIVE_GRANT = 3;
537		public static final int IN_ROSTER = 4;
538		public static final int PENDING_SUBSCRIPTION_REQUEST = 5;
539		public static final int DIRTY_PUSH = 6;
540		public static final int DIRTY_DELETE = 7;
541	}
542}