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