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