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