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 void setPgpKeyId(long keyId) {
316		synchronized (this.keys) {
317			try {
318				this.keys.put("pgp_keyid", keyId);
319			} catch (final JSONException ignored) {
320			}
321		}
322	}
323
324	public void setOption(int option) {
325		this.subscription |= 1 << option;
326	}
327
328	public void resetOption(int option) {
329		this.subscription &= ~(1 << option);
330	}
331
332	public boolean getOption(int option) {
333		return ((this.subscription & (1 << option)) != 0);
334	}
335
336	public boolean showInRoster() {
337		return (this.getOption(Contact.Options.IN_ROSTER) && (!this
338				.getOption(Contact.Options.DIRTY_DELETE)))
339				|| (this.getOption(Contact.Options.DIRTY_PUSH));
340	}
341
342	public boolean showInPhoneBook() {
343		return systemAccount != null && !systemAccount.trim().isEmpty();
344	}
345
346	public void parseSubscriptionFromElement(Element item) {
347		String ask = item.getAttribute("ask");
348		String subscription = item.getAttribute("subscription");
349
350		if (subscription == null) {
351			this.resetOption(Options.FROM);
352			this.resetOption(Options.TO);
353		} else {
354			switch (subscription) {
355				case "to":
356					this.resetOption(Options.FROM);
357					this.setOption(Options.TO);
358					break;
359				case "from":
360					this.resetOption(Options.TO);
361					this.setOption(Options.FROM);
362					this.resetOption(Options.PREEMPTIVE_GRANT);
363					this.resetOption(Options.PENDING_SUBSCRIPTION_REQUEST);
364					break;
365				case "both":
366					this.setOption(Options.TO);
367					this.setOption(Options.FROM);
368					this.resetOption(Options.PREEMPTIVE_GRANT);
369					this.resetOption(Options.PENDING_SUBSCRIPTION_REQUEST);
370					break;
371				case "none":
372					this.resetOption(Options.FROM);
373					this.resetOption(Options.TO);
374					break;
375			}
376		}
377
378		// do NOT override asking if pending push request
379		if (!this.getOption(Contact.Options.DIRTY_PUSH)) {
380			if ((ask != null) && (ask.equals("subscribe"))) {
381				this.setOption(Contact.Options.ASKING);
382			} else {
383				this.resetOption(Contact.Options.ASKING);
384			}
385		}
386	}
387
388	public void parseGroupsFromElement(Element item) {
389		this.groups = new JSONArray();
390		for (Element element : item.getChildren()) {
391			if (element.getName().equals("group") && element.getContent() != null) {
392				this.groups.put(element.getContent());
393			}
394		}
395	}
396
397	public Element asElement() {
398		final Element item = new Element("item");
399		item.setAttribute("jid", this.jid.toString());
400		if (this.serverName != null) {
401			item.setAttribute("name", this.serverName);
402		}
403		for (String group : getGroups(false)) {
404			item.addChild("group").setContent(group);
405		}
406		return item;
407	}
408
409	@Override
410	public int compareTo(@NonNull final ListItem another) {
411		return this.getDisplayName().compareToIgnoreCase(
412				another.getDisplayName());
413	}
414
415	public String getServer() {
416		return getJid().getDomain();
417	}
418
419	public boolean setAvatar(Avatar avatar) {
420		if (this.avatar != null && this.avatar.equals(avatar)) {
421			return false;
422		} else {
423			if (this.avatar != null && this.avatar.origin == Avatar.Origin.PEP && avatar.origin == Avatar.Origin.VCARD) {
424				return false;
425			}
426			this.avatar = avatar;
427			return true;
428		}
429	}
430
431	public String getAvatar() {
432		return avatar == null ? null : avatar.getFilename();
433	}
434
435	public boolean mutualPresenceSubscription() {
436		return getOption(Options.FROM) && getOption(Options.TO);
437	}
438
439	@Override
440	public boolean isBlocked() {
441		return getAccount().isBlocked(this);
442	}
443
444	@Override
445	public boolean isDomainBlocked() {
446		return getAccount().isBlocked(Jid.ofDomain(this.getJid().getDomain()));
447	}
448
449	@Override
450	public Jid getBlockedJid() {
451		if (isDomainBlocked()) {
452			return Jid.ofDomain(getJid().getDomain());
453		} else {
454			return getJid();
455		}
456	}
457
458	public boolean isSelf() {
459		return account.getJid().asBareJid().equals(jid.asBareJid());
460	}
461
462	boolean isOwnServer() {
463		return account.getJid().getDomain().equals(jid.asBareJid().toString());
464	}
465
466	public void setCommonName(String cn) {
467		this.commonName = cn;
468	}
469
470	public void flagActive() {
471		this.mActive = true;
472	}
473
474	public void flagInactive() {
475		this.mActive = false;
476	}
477
478	public boolean isActive() {
479		return this.mActive;
480	}
481
482	public boolean setLastseen(long timestamp) {
483		if (timestamp > this.mLastseen) {
484			this.mLastseen = timestamp;
485			return true;
486		} else {
487			return false;
488		}
489	}
490
491	public long getLastseen() {
492		return this.mLastseen;
493	}
494
495	public void setLastResource(String resource) {
496		this.mLastPresence = resource;
497	}
498
499	public String getLastResource() {
500		return this.mLastPresence;
501	}
502
503	public String getServerName() {
504		return serverName;
505	}
506
507    public final class Options {
508		public static final int TO = 0;
509		public static final int FROM = 1;
510		public static final int ASKING = 2;
511		public static final int PREEMPTIVE_GRANT = 3;
512		public static final int IN_ROSTER = 4;
513		public static final int PENDING_SUBSCRIPTION_REQUEST = 5;
514		public static final int DIRTY_PUSH = 6;
515		public static final int DIRTY_DELETE = 7;
516	}
517}