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		return tags;
160	}
161
162	public boolean match(Context context, String needle) {
163		if (TextUtils.isEmpty(needle)) {
164			return true;
165		}
166		needle = needle.toLowerCase(Locale.US).trim();
167		String[] parts = needle.split("\\s+");
168		if (parts.length > 1) {
169			for (String part : parts) {
170				if (!match(context, part)) {
171					return false;
172				}
173			}
174			return true;
175		} else {
176			return jid.toString().contains(needle) ||
177					getDisplayName().toLowerCase(Locale.US).contains(needle) ||
178					matchInTag(context, needle);
179		}
180	}
181
182	private boolean matchInTag(Context context, String needle) {
183		needle = needle.toLowerCase(Locale.US);
184		for (Tag tag : getTags(context)) {
185			if (tag.getName().toLowerCase(Locale.US).contains(needle)) {
186				return true;
187			}
188		}
189		return false;
190	}
191
192	public ContentValues getContentValues() {
193		synchronized (this.keys) {
194			final ContentValues values = new ContentValues();
195			values.put(ACCOUNT, accountUuid);
196			values.put(SYSTEMNAME, systemName);
197			values.put(SERVERNAME, serverName);
198			values.put(JID, jid.toString());
199			values.put(OPTIONS, subscription);
200			values.put(SYSTEMACCOUNT, systemAccount);
201			values.put(PHOTOURI, photoUri);
202			values.put(KEYS, keys.toString());
203			values.put(AVATAR, avatar == null ? null : avatar.getFilename());
204			values.put(LAST_PRESENCE, mLastPresence);
205			values.put(LAST_TIME, mLastseen);
206			values.put(GROUPS, groups.toString());
207			return values;
208		}
209	}
210
211	public Account getAccount() {
212		return this.account;
213	}
214
215	public void setAccount(Account account) {
216		this.account = account;
217		this.accountUuid = account.getUuid();
218	}
219
220	public Presences getPresences() {
221		return this.presences;
222	}
223
224	public void updatePresence(final String resource, final Presence presence) {
225		this.presences.updatePresence(resource, presence);
226	}
227
228	public void removePresence(final String resource) {
229		this.presences.removePresence(resource);
230	}
231
232	public void clearPresences() {
233		this.presences.clearPresences();
234		this.resetOption(Options.PENDING_SUBSCRIPTION_REQUEST);
235	}
236
237	public Presence.Status getShownStatus() {
238		return this.presences.getShownStatus();
239	}
240
241	public boolean setPhotoUri(String uri) {
242		if (uri != null && !uri.equals(this.photoUri)) {
243			this.photoUri = uri;
244			return true;
245		} else if (this.photoUri != null && uri == null) {
246			this.photoUri = null;
247			return true;
248		} else {
249			return false;
250		}
251	}
252
253	public void setServerName(String serverName) {
254		this.serverName = serverName;
255	}
256
257	public boolean setSystemName(String systemName) {
258		final String old = getDisplayName();
259		this.systemName = systemName;
260		return !old.equals(getDisplayName());
261	}
262
263	public boolean setPresenceName(String presenceName) {
264		final String old = getDisplayName();
265		this.presenceName = presenceName;
266		return !old.equals(getDisplayName());
267	}
268
269	public Uri getSystemAccount() {
270		if (systemAccount == null) {
271			return null;
272		} else {
273			String[] parts = systemAccount.split("#");
274			if (parts.length != 2) {
275				return null;
276			} else {
277				long id = Long.parseLong(parts[0]);
278				return ContactsContract.Contacts.getLookupUri(id, parts[1]);
279			}
280		}
281	}
282
283	public void setSystemAccount(String account) {
284		this.systemAccount = account;
285	}
286
287	private Collection<String> getGroups(final boolean unique) {
288		final Collection<String> groups = unique ? new HashSet<>() : new ArrayList<>();
289		for (int i = 0; i < this.groups.length(); ++i) {
290			try {
291				groups.add(this.groups.getString(i));
292			} catch (final JSONException ignored) {
293			}
294		}
295		return groups;
296	}
297
298	public long getPgpKeyId() {
299		synchronized (this.keys) {
300			if (this.keys.has("pgp_keyid")) {
301				try {
302					return this.keys.getLong("pgp_keyid");
303				} catch (JSONException e) {
304					return 0;
305				}
306			} else {
307				return 0;
308			}
309		}
310	}
311
312	public void setPgpKeyId(long keyId) {
313		synchronized (this.keys) {
314			try {
315				this.keys.put("pgp_keyid", keyId);
316			} catch (final JSONException ignored) {
317			}
318		}
319	}
320
321	public void setOption(int option) {
322		this.subscription |= 1 << option;
323	}
324
325	public void resetOption(int option) {
326		this.subscription &= ~(1 << option);
327	}
328
329	public boolean getOption(int option) {
330		return ((this.subscription & (1 << option)) != 0);
331	}
332
333	public boolean showInRoster() {
334		return (this.getOption(Contact.Options.IN_ROSTER) && (!this
335				.getOption(Contact.Options.DIRTY_DELETE)))
336				|| (this.getOption(Contact.Options.DIRTY_PUSH));
337	}
338
339	public void parseSubscriptionFromElement(Element item) {
340		String ask = item.getAttribute("ask");
341		String subscription = item.getAttribute("subscription");
342
343		if (subscription == null) {
344			this.resetOption(Options.FROM);
345			this.resetOption(Options.TO);
346		} else {
347			switch (subscription) {
348				case "to":
349					this.resetOption(Options.FROM);
350					this.setOption(Options.TO);
351					break;
352				case "from":
353					this.resetOption(Options.TO);
354					this.setOption(Options.FROM);
355					this.resetOption(Options.PREEMPTIVE_GRANT);
356					this.resetOption(Options.PENDING_SUBSCRIPTION_REQUEST);
357					break;
358				case "both":
359					this.setOption(Options.TO);
360					this.setOption(Options.FROM);
361					this.resetOption(Options.PREEMPTIVE_GRANT);
362					this.resetOption(Options.PENDING_SUBSCRIPTION_REQUEST);
363					break;
364				case "none":
365					this.resetOption(Options.FROM);
366					this.resetOption(Options.TO);
367					break;
368			}
369		}
370
371		// do NOT override asking if pending push request
372		if (!this.getOption(Contact.Options.DIRTY_PUSH)) {
373			if ((ask != null) && (ask.equals("subscribe"))) {
374				this.setOption(Contact.Options.ASKING);
375			} else {
376				this.resetOption(Contact.Options.ASKING);
377			}
378		}
379	}
380
381	public void parseGroupsFromElement(Element item) {
382		this.groups = new JSONArray();
383		for (Element element : item.getChildren()) {
384			if (element.getName().equals("group") && element.getContent() != null) {
385				this.groups.put(element.getContent());
386			}
387		}
388	}
389
390	public Element asElement() {
391		final Element item = new Element("item");
392		item.setAttribute("jid", this.jid.toString());
393		if (this.serverName != null) {
394			item.setAttribute("name", this.serverName);
395		}
396		for (String group : getGroups(false)) {
397			item.addChild("group").setContent(group);
398		}
399		return item;
400	}
401
402	@Override
403	public int compareTo(@NonNull final ListItem another) {
404		return this.getDisplayName().compareToIgnoreCase(
405				another.getDisplayName());
406	}
407
408	public String getServer() {
409		return getJid().getDomain();
410	}
411
412	public boolean setAvatar(Avatar avatar) {
413		if (this.avatar != null && this.avatar.equals(avatar)) {
414			return false;
415		} else {
416			if (this.avatar != null && this.avatar.origin == Avatar.Origin.PEP && avatar.origin == Avatar.Origin.VCARD) {
417				return false;
418			}
419			this.avatar = avatar;
420			return true;
421		}
422	}
423
424	public String getAvatar() {
425		return avatar == null ? null : avatar.getFilename();
426	}
427
428	public boolean mutualPresenceSubscription() {
429		return getOption(Options.FROM) && getOption(Options.TO);
430	}
431
432	@Override
433	public boolean isBlocked() {
434		return getAccount().isBlocked(this);
435	}
436
437	@Override
438	public boolean isDomainBlocked() {
439		return getAccount().isBlocked(Jid.ofDomain(this.getJid().getDomain()));
440	}
441
442	@Override
443	public Jid getBlockedJid() {
444		if (isDomainBlocked()) {
445			return Jid.ofDomain(getJid().getDomain());
446		} else {
447			return getJid();
448		}
449	}
450
451	public boolean isSelf() {
452		return account.getJid().asBareJid().equals(jid.asBareJid());
453	}
454
455	boolean isOwnServer() {
456		return account.getJid().getDomain().equals(jid.asBareJid().toString());
457	}
458
459	public void setCommonName(String cn) {
460		this.commonName = cn;
461	}
462
463	public void flagActive() {
464		this.mActive = true;
465	}
466
467	public void flagInactive() {
468		this.mActive = false;
469	}
470
471	public boolean isActive() {
472		return this.mActive;
473	}
474
475	public boolean setLastseen(long timestamp) {
476		if (timestamp > this.mLastseen) {
477			this.mLastseen = timestamp;
478			return true;
479		} else {
480			return false;
481		}
482	}
483
484	public long getLastseen() {
485		return this.mLastseen;
486	}
487
488	public void setLastResource(String resource) {
489		this.mLastPresence = resource;
490	}
491
492	public String getLastResource() {
493		return this.mLastPresence;
494	}
495
496	public String getServerName() {
497		return serverName;
498	}
499
500    public final class Options {
501		public static final int TO = 0;
502		public static final int FROM = 1;
503		public static final int ASKING = 2;
504		public static final int PREEMPTIVE_GRANT = 3;
505		public static final int IN_ROSTER = 4;
506		public static final int PENDING_SUBSCRIPTION_REQUEST = 5;
507		public static final int DIRTY_PUSH = 6;
508		public static final int DIRTY_DELETE = 7;
509	}
510}