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 && trusted()) {
122			return this.presenceName;
123		} else if (jid.hasLocalpart()) {
124			return jid.getLocalpart();
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.toString());
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 int getSubscription() {
213		return this.subscription;
214	}
215
216	public Account getAccount() {
217		return this.account;
218	}
219
220	public void setAccount(Account account) {
221		this.account = account;
222		this.accountUuid = account.getUuid();
223	}
224
225	public Presences getPresences() {
226		return this.presences;
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 getShownStatus() {
243		return this.presences.getShownStatus();
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 Uri getSystemAccount() {
271		if (systemAccount == null) {
272			return null;
273		} else {
274			String[] parts = systemAccount.split("#");
275			if (parts.length != 2) {
276				return null;
277			} else {
278				long id = Long.parseLong(parts[0]);
279				return ContactsContract.Contacts.getLookupUri(id, parts[1]);
280			}
281		}
282	}
283
284	public void setSystemAccount(String account) {
285		this.systemAccount = account;
286	}
287
288	public List<String> getGroups() {
289		ArrayList<String> groups = new ArrayList<String>();
290		for (int i = 0; i < this.groups.length(); ++i) {
291			try {
292				groups.add(this.groups.getString(i));
293			} catch (final JSONException ignored) {
294			}
295		}
296		return groups;
297	}
298
299	public ArrayList<String> getOtrFingerprints() {
300		synchronized (this.keys) {
301			final ArrayList<String> fingerprints = new ArrayList<String>();
302			try {
303				if (this.keys.has("otr_fingerprints")) {
304					final JSONArray prints = this.keys.getJSONArray("otr_fingerprints");
305					for (int i = 0; i < prints.length(); ++i) {
306						final String print = prints.isNull(i) ? null : prints.getString(i);
307						if (print != null && !print.isEmpty()) {
308							fingerprints.add(prints.getString(i));
309						}
310					}
311				}
312			} catch (final JSONException ignored) {
313
314			}
315			return fingerprints;
316		}
317	}
318	public boolean addOtrFingerprint(String print) {
319		synchronized (this.keys) {
320			if (getOtrFingerprints().contains(print)) {
321				return false;
322			}
323			try {
324				JSONArray fingerprints;
325				if (!this.keys.has("otr_fingerprints")) {
326					fingerprints = new JSONArray();
327				} else {
328					fingerprints = this.keys.getJSONArray("otr_fingerprints");
329				}
330				fingerprints.put(print);
331				this.keys.put("otr_fingerprints", fingerprints);
332				return true;
333			} catch (final JSONException ignored) {
334				return false;
335			}
336		}
337	}
338
339	public long getPgpKeyId() {
340		synchronized (this.keys) {
341			if (this.keys.has("pgp_keyid")) {
342				try {
343					return this.keys.getLong("pgp_keyid");
344				} catch (JSONException e) {
345					return 0;
346				}
347			} else {
348				return 0;
349			}
350		}
351	}
352
353	public void setPgpKeyId(long keyId) {
354		synchronized (this.keys) {
355			try {
356				this.keys.put("pgp_keyid", keyId);
357			} catch (final JSONException ignored) {
358			}
359		}
360	}
361
362	public void setOption(int option) {
363		this.subscription |= 1 << option;
364	}
365
366	public void resetOption(int option) {
367		this.subscription &= ~(1 << option);
368	}
369
370	public boolean getOption(int option) {
371		return ((this.subscription & (1 << option)) != 0);
372	}
373
374	public boolean showInRoster() {
375		return (this.getOption(Contact.Options.IN_ROSTER) && (!this
376					.getOption(Contact.Options.DIRTY_DELETE)))
377			|| (this.getOption(Contact.Options.DIRTY_PUSH));
378	}
379
380	public void parseSubscriptionFromElement(Element item) {
381		String ask = item.getAttribute("ask");
382		String subscription = item.getAttribute("subscription");
383
384		if (subscription != null) {
385			switch (subscription) {
386				case "to":
387					this.resetOption(Options.FROM);
388					this.setOption(Options.TO);
389					break;
390				case "from":
391					this.resetOption(Options.TO);
392					this.setOption(Options.FROM);
393					this.resetOption(Options.PREEMPTIVE_GRANT);
394					this.resetOption(Options.PENDING_SUBSCRIPTION_REQUEST);
395					break;
396				case "both":
397					this.setOption(Options.TO);
398					this.setOption(Options.FROM);
399					this.resetOption(Options.PREEMPTIVE_GRANT);
400					this.resetOption(Options.PENDING_SUBSCRIPTION_REQUEST);
401					break;
402				case "none":
403					this.resetOption(Options.FROM);
404					this.resetOption(Options.TO);
405					break;
406			}
407		}
408
409		// do NOT override asking if pending push request
410		if (!this.getOption(Contact.Options.DIRTY_PUSH)) {
411			if ((ask != null) && (ask.equals("subscribe"))) {
412				this.setOption(Contact.Options.ASKING);
413			} else {
414				this.resetOption(Contact.Options.ASKING);
415			}
416		}
417	}
418
419	public void parseGroupsFromElement(Element item) {
420		this.groups = new JSONArray();
421		for (Element element : item.getChildren()) {
422			if (element.getName().equals("group") && element.getContent() != null) {
423				this.groups.put(element.getContent());
424			}
425		}
426	}
427
428	public Element asElement() {
429		final Element item = new Element("item");
430		item.setAttribute("jid", this.jid.toString());
431		if (this.serverName != null) {
432			item.setAttribute("name", this.serverName);
433		}
434		for (String group : getGroups()) {
435			item.addChild("group").setContent(group);
436		}
437		return item;
438	}
439
440	@Override
441	public int compareTo(final ListItem another) {
442		return this.getDisplayName().compareToIgnoreCase(
443				another.getDisplayName());
444	}
445
446	public Jid getServer() {
447		return getJid().toDomainJid();
448	}
449
450	public boolean setAvatar(Avatar avatar) {
451		if (this.avatar != null && this.avatar.equals(avatar)) {
452			return false;
453		} else {
454			if (this.avatar != null && this.avatar.origin == Avatar.Origin.PEP && avatar.origin == Avatar.Origin.VCARD) {
455				return false;
456			}
457			this.avatar = avatar;
458			return true;
459		}
460	}
461
462	public String getAvatar() {
463		return avatar == null ? null : avatar.getFilename();
464	}
465
466	public boolean deleteOtrFingerprint(String fingerprint) {
467		synchronized (this.keys) {
468			boolean success = false;
469			try {
470				if (this.keys.has("otr_fingerprints")) {
471					JSONArray newPrints = new JSONArray();
472					JSONArray oldPrints = this.keys
473							.getJSONArray("otr_fingerprints");
474					for (int i = 0; i < oldPrints.length(); ++i) {
475						if (!oldPrints.getString(i).equals(fingerprint)) {
476							newPrints.put(oldPrints.getString(i));
477						} else {
478							success = true;
479						}
480					}
481					this.keys.put("otr_fingerprints", newPrints);
482				}
483				return success;
484			} catch (JSONException e) {
485				return false;
486			}
487		}
488	}
489
490	public boolean trusted() {
491		return getOption(Options.FROM) && getOption(Options.TO);
492	}
493
494	@Override
495	public boolean isBlocked() {
496		return getAccount().isBlocked(this);
497	}
498
499	@Override
500	public boolean isDomainBlocked() {
501		return getAccount().isBlocked(this.getJid().toDomainJid());
502	}
503
504	@Override
505	public Jid getBlockedJid() {
506		if (isDomainBlocked()) {
507			return getJid().toDomainJid();
508		} else {
509			return getJid();
510		}
511	}
512
513	public boolean isSelf() {
514		return account.getJid().toBareJid().equals(getJid().toBareJid());
515	}
516
517	public void setCommonName(String cn) {
518		this.commonName = cn;
519	}
520
521	public void flagActive() {
522		this.mActive = true;
523	}
524
525	public void flagInactive() {
526		this.mActive = false;
527	}
528
529	public boolean isActive() {
530		return this.mActive;
531	}
532
533	public void setLastseen(long timestamp) {
534		this.mLastseen = Math.max(timestamp, mLastseen);
535	}
536
537	public long getLastseen() {
538		return this.mLastseen;
539	}
540
541	public void setLastResource(String resource) {
542		this.mLastPresence = resource;
543	}
544
545	public String getLastResource() {
546		return this.mLastPresence;
547	}
548
549	public final class Options {
550		public static final int TO = 0;
551		public static final int FROM = 1;
552		public static final int ASKING = 2;
553		public static final int PREEMPTIVE_GRANT = 3;
554		public static final int IN_ROSTER = 4;
555		public static final int PENDING_SUBSCRIPTION_REQUEST = 5;
556		public static final int DIRTY_PUSH = 6;
557		public static final int DIRTY_DELETE = 7;
558	}
559}