Contact.java

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