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