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