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