store avatars in cache folder

Daniel Gultsch created

Change summary

src/main/java/eu/siacs/conversations/entities/Contact.java               | 1098 
src/main/java/eu/siacs/conversations/parser/MessageParser.java           |   13 
src/main/java/eu/siacs/conversations/parser/PresenceParser.java          |   14 
src/main/java/eu/siacs/conversations/persistance/FileBackend.java        |   40 
src/main/java/eu/siacs/conversations/services/AvatarService.java         |   32 
src/main/java/eu/siacs/conversations/services/XmppConnectionService.java |   37 
6 files changed, 619 insertions(+), 615 deletions(-)

Detailed changes

src/main/java/eu/siacs/conversations/entities/Contact.java 🔗

@@ -29,554 +29,552 @@ import eu.siacs.conversations.xmpp.pep.Avatar;
 import eu.siacs.conversations.xmpp.Jid;
 
 public class Contact implements ListItem, Blockable {
-	public static final String TABLENAME = "contacts";
-
-	public static final String SYSTEMNAME = "systemname";
-	public static final String SERVERNAME = "servername";
-	public static final String PRESENCE_NAME = "presence_name";
-	public static final String JID = "jid";
-	public static final String OPTIONS = "options";
-	public static final String SYSTEMACCOUNT = "systemaccount";
-	public static final String PHOTOURI = "photouri";
-	public static final String KEYS = "pgpkey";
-	public static final String ACCOUNT = "accountUuid";
-	public static final String AVATAR = "avatar";
-	public static final String LAST_PRESENCE = "last_presence";
-	public static final String LAST_TIME = "last_time";
-	public static final String GROUPS = "groups";
-	private String accountUuid;
-	private String systemName;
-	private String serverName;
-	private String presenceName;
-	private String commonName;
-	protected Jid jid;
-	private int subscription = 0;
-	private Uri systemAccount;
-	private String photoUri;
-	private final JSONObject keys;
-	private JSONArray groups = new JSONArray();
-	private final Presences presences = new Presences();
-	protected Account account;
-	protected Avatar avatar;
-
-	private boolean mActive = false;
-	private long mLastseen = 0;
-	private String mLastPresence = null;
-
-	public Contact(final String account, final String systemName, final String serverName, final String presenceName,
-	               final Jid jid, final int subscription, final String photoUri,
-	               final Uri systemAccount, final String keys, final String avatar, final long lastseen,
-	               final String presence, final String groups) {
-		this.accountUuid = account;
-		this.systemName = systemName;
-		this.serverName = serverName;
-		this.presenceName = presenceName;
-		this.jid = jid;
-		this.subscription = subscription;
-		this.photoUri = photoUri;
-		this.systemAccount = systemAccount;
-		JSONObject tmpJsonObject;
-		try {
-			tmpJsonObject = (keys == null ? new JSONObject("") : new JSONObject(keys));
-		} catch (JSONException e) {
-			tmpJsonObject = new JSONObject();
-		}
-		this.keys = tmpJsonObject;
-		if (avatar != null) {
-			this.avatar = new Avatar();
-			this.avatar.sha1sum = avatar;
-			this.avatar.origin = Avatar.Origin.VCARD; //always assume worst
-		}
-		try {
-			this.groups = (groups == null ? new JSONArray() : new JSONArray(groups));
-		} catch (JSONException e) {
-			this.groups = new JSONArray();
-		}
-		this.mLastseen = lastseen;
-		this.mLastPresence = presence;
-	}
-
-	public Contact(final Jid jid) {
-		this.jid = jid;
-		this.keys = new JSONObject();
-	}
-
-	public static Contact fromCursor(final Cursor cursor) {
-		final Jid jid;
-		try {
-			jid = Jid.of(cursor.getString(cursor.getColumnIndex(JID)));
-		} catch (final IllegalArgumentException e) {
-			// TODO: Borked DB... handle this somehow?
-			return null;
-		}
-		Uri systemAccount;
-		try {
-			systemAccount = Uri.parse(cursor.getString(cursor.getColumnIndex(SYSTEMACCOUNT)));
-		} catch (Exception e) {
-			systemAccount = null;
-		}
-		return new Contact(cursor.getString(cursor.getColumnIndex(ACCOUNT)),
-				cursor.getString(cursor.getColumnIndex(SYSTEMNAME)),
-				cursor.getString(cursor.getColumnIndex(SERVERNAME)),
-				cursor.getString(cursor.getColumnIndex(PRESENCE_NAME)),
-				jid,
-				cursor.getInt(cursor.getColumnIndex(OPTIONS)),
-				cursor.getString(cursor.getColumnIndex(PHOTOURI)),
-				systemAccount,
-				cursor.getString(cursor.getColumnIndex(KEYS)),
-				cursor.getString(cursor.getColumnIndex(AVATAR)),
-				cursor.getLong(cursor.getColumnIndex(LAST_TIME)),
-				cursor.getString(cursor.getColumnIndex(LAST_PRESENCE)),
-				cursor.getString(cursor.getColumnIndex(GROUPS)));
-	}
-
-	public String getDisplayName() {
-		if (Config.X509_VERIFICATION && !TextUtils.isEmpty(this.commonName)) {
-			return this.commonName;
-		} else if (!TextUtils.isEmpty(this.systemName)) {
-			return this.systemName;
-		} else if (!TextUtils.isEmpty(this.serverName)) {
-			return this.serverName;
-		} else if (!TextUtils.isEmpty(this.presenceName) && ((QuickConversationsService.isQuicksy() && JidHelper.isQuicksyDomain(jid.getDomain())) ||mutualPresenceSubscription())) {
-			return this.presenceName;
-		} else if (jid.getLocal() != null) {
-			return JidHelper.localPartOrFallback(jid);
-		} else {
-			return jid.getDomain().toEscapedString();
-		}
-	}
-
-	public String getPublicDisplayName() {
-		if (!TextUtils.isEmpty(this.presenceName)) {
-			return this.presenceName;
-		} else if (jid.getLocal() != null) {
-			return JidHelper.localPartOrFallback(jid);
-		} else {
-			return jid.getDomain().toEscapedString();
-		}
-	}
-
-	public String getProfilePhoto() {
-		return this.photoUri;
-	}
-
-	public Jid getJid() {
-		return jid;
-	}
-
-	@Override
-	public List<Tag> getTags(Context context) {
-		final ArrayList<Tag> tags = new ArrayList<>();
-		for (final String group : getGroups(true)) {
-			tags.add(new Tag(group, UIHelper.getColorForName(group)));
-		}
-		Presence.Status status = getShownStatus();
-		if (status != Presence.Status.OFFLINE) {
-			tags.add(UIHelper.getTagForStatus(context, status));
-		}
-		if (isBlocked()) {
-			tags.add(new Tag(context.getString(R.string.blocked), 0xff2e2f3b));
-		}
-		return tags;
-	}
-
-	public boolean match(Context context, String needle) {
-		if (TextUtils.isEmpty(needle)) {
-			return true;
-		}
-		needle = needle.toLowerCase(Locale.US).trim();
-		String[] parts = needle.split("\\s+");
-		if (parts.length > 1) {
-			for (String part : parts) {
-				if (!match(context, part)) {
-					return false;
-				}
-			}
-			return true;
-		} else {
-			return jid.toString().contains(needle) ||
-					getDisplayName().toLowerCase(Locale.US).contains(needle) ||
-					matchInTag(context, needle);
-		}
-	}
-
-	private boolean matchInTag(Context context, String needle) {
-		needle = needle.toLowerCase(Locale.US);
-		for (Tag tag : getTags(context)) {
-			if (tag.getName().toLowerCase(Locale.US).contains(needle)) {
-				return true;
-			}
-		}
-		return false;
-	}
-
-	public ContentValues getContentValues() {
-		synchronized (this.keys) {
-			final ContentValues values = new ContentValues();
-			values.put(ACCOUNT, accountUuid);
-			values.put(SYSTEMNAME, systemName);
-			values.put(SERVERNAME, serverName);
-			values.put(PRESENCE_NAME, presenceName);
-			values.put(JID, jid.toString());
-			values.put(OPTIONS, subscription);
-			values.put(SYSTEMACCOUNT, systemAccount != null ? systemAccount.toString() : null);
-			values.put(PHOTOURI, photoUri);
-			values.put(KEYS, keys.toString());
-			values.put(AVATAR, avatar == null ? null : avatar.getFilename());
-			values.put(LAST_PRESENCE, mLastPresence);
-			values.put(LAST_TIME, mLastseen);
-			values.put(GROUPS, groups.toString());
-			return values;
-		}
-	}
-
-	public Account getAccount() {
-		return this.account;
-	}
-
-	public void setAccount(Account account) {
-		this.account = account;
-		this.accountUuid = account.getUuid();
-	}
-
-	public Presences getPresences() {
-		return this.presences;
-	}
-
-	public void updatePresence(final String resource, final Presence presence) {
-		this.presences.updatePresence(resource, presence);
-	}
-
-	public void removePresence(final String resource) {
-		this.presences.removePresence(resource);
-	}
-
-	public void clearPresences() {
-		this.presences.clearPresences();
-		this.resetOption(Options.PENDING_SUBSCRIPTION_REQUEST);
-	}
-
-	public Presence.Status getShownStatus() {
-		return this.presences.getShownStatus();
-	}
-
-	public boolean setPhotoUri(String uri) {
-		if (uri != null && !uri.equals(this.photoUri)) {
-			this.photoUri = uri;
-			return true;
-		} else if (this.photoUri != null && uri == null) {
-			this.photoUri = null;
-			return true;
-		} else {
-			return false;
-		}
-	}
-
-	public void setServerName(String serverName) {
-		this.serverName = serverName;
-	}
-
-	public boolean setSystemName(String systemName) {
-		final String old = getDisplayName();
-		this.systemName = systemName;
-		return !old.equals(getDisplayName());
-	}
-
-	public boolean setPresenceName(String presenceName) {
-		final String old = getDisplayName();
-		this.presenceName = presenceName;
-		return !old.equals(getDisplayName());
-	}
-
-	public Uri getSystemAccount() {
-		return systemAccount;
-	}
-
-	public void setSystemAccount(Uri lookupUri) {
-		this.systemAccount = lookupUri;
-	}
-
-	private Collection<String> getGroups(final boolean unique) {
-		final Collection<String> groups = unique ? new HashSet<>() : new ArrayList<>();
-		for (int i = 0; i < this.groups.length(); ++i) {
-			try {
-				groups.add(this.groups.getString(i));
-			} catch (final JSONException ignored) {
-			}
-		}
-		return groups;
-	}
-
-	public long getPgpKeyId() {
-		synchronized (this.keys) {
-			if (this.keys.has("pgp_keyid")) {
-				try {
-					return this.keys.getLong("pgp_keyid");
-				} catch (JSONException e) {
-					return 0;
-				}
-			} else {
-				return 0;
-			}
-		}
-	}
-
-	public boolean setPgpKeyId(long keyId) {
-		final long previousKeyId = getPgpKeyId();
-		synchronized (this.keys) {
-			try {
-				this.keys.put("pgp_keyid", keyId);
-				return previousKeyId != keyId;
-			} catch (final JSONException ignored) {
-			}
-		}
-		return false;
-	}
-
-	public void setOption(int option) {
-		this.subscription |= 1 << option;
-	}
-
-	public void resetOption(int option) {
-		this.subscription &= ~(1 << option);
-	}
-
-	public boolean getOption(int option) {
-		return ((this.subscription & (1 << option)) != 0);
-	}
-
-	public boolean showInRoster() {
-		return (this.getOption(Contact.Options.IN_ROSTER) && (!this
-				.getOption(Contact.Options.DIRTY_DELETE)))
-				|| (this.getOption(Contact.Options.DIRTY_PUSH));
-	}
-
-	public boolean showInContactList() {
-		return showInRoster()
-				|| getOption(Options.SYNCED_VIA_OTHER)
-				|| (QuickConversationsService.isQuicksy() && systemAccount != null);
-	}
-
-	public void parseSubscriptionFromElement(Element item) {
-		String ask = item.getAttribute("ask");
-		String subscription = item.getAttribute("subscription");
-
-		if (subscription == null) {
-			this.resetOption(Options.FROM);
-			this.resetOption(Options.TO);
-		} else {
-			switch (subscription) {
-				case "to":
-					this.resetOption(Options.FROM);
-					this.setOption(Options.TO);
-					break;
-				case "from":
-					this.resetOption(Options.TO);
-					this.setOption(Options.FROM);
-					this.resetOption(Options.PREEMPTIVE_GRANT);
-					this.resetOption(Options.PENDING_SUBSCRIPTION_REQUEST);
-					break;
-				case "both":
-					this.setOption(Options.TO);
-					this.setOption(Options.FROM);
-					this.resetOption(Options.PREEMPTIVE_GRANT);
-					this.resetOption(Options.PENDING_SUBSCRIPTION_REQUEST);
-					break;
-				case "none":
-					this.resetOption(Options.FROM);
-					this.resetOption(Options.TO);
-					break;
-			}
-		}
-
-		// do NOT override asking if pending push request
-		if (!this.getOption(Contact.Options.DIRTY_PUSH)) {
-			if ((ask != null) && (ask.equals("subscribe"))) {
-				this.setOption(Contact.Options.ASKING);
-			} else {
-				this.resetOption(Contact.Options.ASKING);
-			}
-		}
-	}
-
-	public void parseGroupsFromElement(Element item) {
-		this.groups = new JSONArray();
-		for (Element element : item.getChildren()) {
-			if (element.getName().equals("group") && element.getContent() != null) {
-				this.groups.put(element.getContent());
-			}
-		}
-	}
-
-	public Element asElement() {
-		final Element item = new Element("item");
-		item.setAttribute("jid", this.jid);
-		if (this.serverName != null) {
-			item.setAttribute("name", this.serverName);
-		}
-		for (String group : getGroups(false)) {
-			item.addChild("group").setContent(group);
-		}
-		return item;
-	}
-
-	@Override
-	public int compareTo(@NonNull final ListItem another) {
-		return this.getDisplayName().compareToIgnoreCase(
-				another.getDisplayName());
-	}
-
-	public String getServer() {
-		return getJid().getDomain().toEscapedString();
-	}
-
-	public boolean setAvatar(Avatar avatar) {
-		return setAvatar(avatar, false);
-	}
-
-	public boolean setAvatar(Avatar avatar, boolean previouslyOmittedPepFetch) {
-		if (this.avatar != null && this.avatar.equals(avatar)) {
-			return false;
-		} else {
-			if (!previouslyOmittedPepFetch && this.avatar != null && this.avatar.origin == Avatar.Origin.PEP && avatar.origin == Avatar.Origin.VCARD) {
-				return false;
-			}
-			this.avatar = avatar;
-			return true;
-		}
-	}
-
-	public String getAvatarFilename() {
-		return avatar == null ? null : avatar.getFilename();
-	}
-
-	public Avatar getAvatar() {
-		return avatar;
-	}
-
-	public boolean mutualPresenceSubscription() {
-		return getOption(Options.FROM) && getOption(Options.TO);
-	}
-
-	@Override
-	public boolean isBlocked() {
-		return getAccount().isBlocked(this);
-	}
-
-	@Override
-	public boolean isDomainBlocked() {
-		return getAccount().isBlocked(this.getJid().getDomain());
-	}
-
-	@Override
-	public Jid getBlockedJid() {
-		if (isDomainBlocked()) {
-			return getJid().getDomain();
-		} else {
-			return getJid();
-		}
-	}
-
-	public boolean isSelf() {
-		return account.getJid().asBareJid().equals(jid.asBareJid());
-	}
-
-	boolean isOwnServer() {
-		return account.getJid().getDomain().equals(jid.asBareJid());
-	}
-
-	public void setCommonName(String cn) {
-		this.commonName = cn;
-	}
-
-	public void flagActive() {
-		this.mActive = true;
-	}
-
-	public void flagInactive() {
-		this.mActive = false;
-	}
-
-	public boolean isActive() {
-		return this.mActive;
-	}
-
-	public boolean setLastseen(long timestamp) {
-		if (timestamp > this.mLastseen) {
-			this.mLastseen = timestamp;
-			return true;
-		} else {
-			return false;
-		}
-	}
-
-	public long getLastseen() {
-		return this.mLastseen;
-	}
-
-	public void setLastResource(String resource) {
-		this.mLastPresence = resource;
-	}
-
-	public String getLastResource() {
-		return this.mLastPresence;
-	}
-
-	public String getServerName() {
-		return serverName;
-	}
-
-	public synchronized boolean setPhoneContact(AbstractPhoneContact phoneContact) {
-		setOption(getOption(phoneContact.getClass()));
-		setSystemAccount(phoneContact.getLookupUri());
-		boolean changed = setSystemName(phoneContact.getDisplayName());
-		changed |= setPhotoUri(phoneContact.getPhotoUri());
-		return changed;
-	}
-
-	public synchronized boolean unsetPhoneContact(Class<?extends AbstractPhoneContact> clazz) {
-		resetOption(getOption(clazz));
-		boolean changed = false;
-		if (!getOption(Options.SYNCED_VIA_ADDRESSBOOK) && !getOption(Options.SYNCED_VIA_OTHER)) {
-			setSystemAccount(null);
-			changed |= setPhotoUri(null);
-			changed |= setSystemName(null);
-		}
-		return changed;
-	}
-
-	public static int getOption(Class<? extends AbstractPhoneContact> clazz) {
-		if (clazz == JabberIdContact.class) {
-			return Options.SYNCED_VIA_ADDRESSBOOK;
-		} else {
-			return Options.SYNCED_VIA_OTHER;
-		}
-	}
-
-	@Override
-	public int getAvatarBackgroundColor() {
-		return UIHelper.getColorForName(jid != null ? jid.asBareJid().toString() : getDisplayName());
-	}
-
-	@Override
-	public String getAvatarName() {
-		return getDisplayName();
-	}
-
-	public boolean hasAvatarOrPresenceName() {
-		return (avatar != null && avatar.getFilename() != null) || presenceName != null;
-	}
-
-	public final class Options {
-		public static final int TO = 0;
-		public static final int FROM = 1;
-		public static final int ASKING = 2;
-		public static final int PREEMPTIVE_GRANT = 3;
-		public static final int IN_ROSTER = 4;
-		public static final int PENDING_SUBSCRIPTION_REQUEST = 5;
-		public static final int DIRTY_PUSH = 6;
-		public static final int DIRTY_DELETE = 7;
-		private static final int SYNCED_VIA_ADDRESSBOOK = 8;
-		public static final int SYNCED_VIA_OTHER = 9;
-	}
+    public static final String TABLENAME = "contacts";
+
+    public static final String SYSTEMNAME = "systemname";
+    public static final String SERVERNAME = "servername";
+    public static final String PRESENCE_NAME = "presence_name";
+    public static final String JID = "jid";
+    public static final String OPTIONS = "options";
+    public static final String SYSTEMACCOUNT = "systemaccount";
+    public static final String PHOTOURI = "photouri";
+    public static final String KEYS = "pgpkey";
+    public static final String ACCOUNT = "accountUuid";
+    public static final String AVATAR = "avatar";
+    public static final String LAST_PRESENCE = "last_presence";
+    public static final String LAST_TIME = "last_time";
+    public static final String GROUPS = "groups";
+    private String accountUuid;
+    private String systemName;
+    private String serverName;
+    private String presenceName;
+    private String commonName;
+    protected Jid jid;
+    private int subscription = 0;
+    private Uri systemAccount;
+    private String photoUri;
+    private final JSONObject keys;
+    private JSONArray groups = new JSONArray();
+    private final Presences presences = new Presences();
+    protected Account account;
+    protected Avatar avatar;
+
+    private boolean mActive = false;
+    private long mLastseen = 0;
+    private String mLastPresence = null;
+
+    public Contact(final String account, final String systemName, final String serverName, final String presenceName,
+                   final Jid jid, final int subscription, final String photoUri,
+                   final Uri systemAccount, final String keys, final String avatar, final long lastseen,
+                   final String presence, final String groups) {
+        this.accountUuid = account;
+        this.systemName = systemName;
+        this.serverName = serverName;
+        this.presenceName = presenceName;
+        this.jid = jid;
+        this.subscription = subscription;
+        this.photoUri = photoUri;
+        this.systemAccount = systemAccount;
+        JSONObject tmpJsonObject;
+        try {
+            tmpJsonObject = (keys == null ? new JSONObject("") : new JSONObject(keys));
+        } catch (JSONException e) {
+            tmpJsonObject = new JSONObject();
+        }
+        this.keys = tmpJsonObject;
+        if (avatar != null) {
+            this.avatar = new Avatar();
+            this.avatar.sha1sum = avatar;
+            this.avatar.origin = Avatar.Origin.VCARD; //always assume worst
+        }
+        try {
+            this.groups = (groups == null ? new JSONArray() : new JSONArray(groups));
+        } catch (JSONException e) {
+            this.groups = new JSONArray();
+        }
+        this.mLastseen = lastseen;
+        this.mLastPresence = presence;
+    }
+
+    public Contact(final Jid jid) {
+        this.jid = jid;
+        this.keys = new JSONObject();
+    }
+
+    public static Contact fromCursor(final Cursor cursor) {
+        final Jid jid;
+        try {
+            jid = Jid.of(cursor.getString(cursor.getColumnIndex(JID)));
+        } catch (final IllegalArgumentException e) {
+            // TODO: Borked DB... handle this somehow?
+            return null;
+        }
+        Uri systemAccount;
+        try {
+            systemAccount = Uri.parse(cursor.getString(cursor.getColumnIndex(SYSTEMACCOUNT)));
+        } catch (Exception e) {
+            systemAccount = null;
+        }
+        return new Contact(cursor.getString(cursor.getColumnIndex(ACCOUNT)),
+                cursor.getString(cursor.getColumnIndex(SYSTEMNAME)),
+                cursor.getString(cursor.getColumnIndex(SERVERNAME)),
+                cursor.getString(cursor.getColumnIndex(PRESENCE_NAME)),
+                jid,
+                cursor.getInt(cursor.getColumnIndex(OPTIONS)),
+                cursor.getString(cursor.getColumnIndex(PHOTOURI)),
+                systemAccount,
+                cursor.getString(cursor.getColumnIndex(KEYS)),
+                cursor.getString(cursor.getColumnIndex(AVATAR)),
+                cursor.getLong(cursor.getColumnIndex(LAST_TIME)),
+                cursor.getString(cursor.getColumnIndex(LAST_PRESENCE)),
+                cursor.getString(cursor.getColumnIndex(GROUPS)));
+    }
+
+    public String getDisplayName() {
+        if (Config.X509_VERIFICATION && !TextUtils.isEmpty(this.commonName)) {
+            return this.commonName;
+        } else if (!TextUtils.isEmpty(this.systemName)) {
+            return this.systemName;
+        } else if (!TextUtils.isEmpty(this.serverName)) {
+            return this.serverName;
+        } else if (!TextUtils.isEmpty(this.presenceName) && ((QuickConversationsService.isQuicksy() && JidHelper.isQuicksyDomain(jid.getDomain())) || mutualPresenceSubscription())) {
+            return this.presenceName;
+        } else if (jid.getLocal() != null) {
+            return JidHelper.localPartOrFallback(jid);
+        } else {
+            return jid.getDomain().toEscapedString();
+        }
+    }
+
+    public String getPublicDisplayName() {
+        if (!TextUtils.isEmpty(this.presenceName)) {
+            return this.presenceName;
+        } else if (jid.getLocal() != null) {
+            return JidHelper.localPartOrFallback(jid);
+        } else {
+            return jid.getDomain().toEscapedString();
+        }
+    }
+
+    public String getProfilePhoto() {
+        return this.photoUri;
+    }
+
+    public Jid getJid() {
+        return jid;
+    }
+
+    @Override
+    public List<Tag> getTags(Context context) {
+        final ArrayList<Tag> tags = new ArrayList<>();
+        for (final String group : getGroups(true)) {
+            tags.add(new Tag(group, UIHelper.getColorForName(group)));
+        }
+        Presence.Status status = getShownStatus();
+        if (status != Presence.Status.OFFLINE) {
+            tags.add(UIHelper.getTagForStatus(context, status));
+        }
+        if (isBlocked()) {
+            tags.add(new Tag(context.getString(R.string.blocked), 0xff2e2f3b));
+        }
+        return tags;
+    }
+
+    public boolean match(Context context, String needle) {
+        if (TextUtils.isEmpty(needle)) {
+            return true;
+        }
+        needle = needle.toLowerCase(Locale.US).trim();
+        String[] parts = needle.split("\\s+");
+        if (parts.length > 1) {
+            for (String part : parts) {
+                if (!match(context, part)) {
+                    return false;
+                }
+            }
+            return true;
+        } else {
+            return jid.toString().contains(needle) ||
+                    getDisplayName().toLowerCase(Locale.US).contains(needle) ||
+                    matchInTag(context, needle);
+        }
+    }
+
+    private boolean matchInTag(Context context, String needle) {
+        needle = needle.toLowerCase(Locale.US);
+        for (Tag tag : getTags(context)) {
+            if (tag.getName().toLowerCase(Locale.US).contains(needle)) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    public ContentValues getContentValues() {
+        synchronized (this.keys) {
+            final ContentValues values = new ContentValues();
+            values.put(ACCOUNT, accountUuid);
+            values.put(SYSTEMNAME, systemName);
+            values.put(SERVERNAME, serverName);
+            values.put(PRESENCE_NAME, presenceName);
+            values.put(JID, jid.toString());
+            values.put(OPTIONS, subscription);
+            values.put(SYSTEMACCOUNT, systemAccount != null ? systemAccount.toString() : null);
+            values.put(PHOTOURI, photoUri);
+            values.put(KEYS, keys.toString());
+            values.put(AVATAR, avatar == null ? null : avatar.getFilename());
+            values.put(LAST_PRESENCE, mLastPresence);
+            values.put(LAST_TIME, mLastseen);
+            values.put(GROUPS, groups.toString());
+            return values;
+        }
+    }
+
+    public Account getAccount() {
+        return this.account;
+    }
+
+    public void setAccount(Account account) {
+        this.account = account;
+        this.accountUuid = account.getUuid();
+    }
+
+    public Presences getPresences() {
+        return this.presences;
+    }
+
+    public void updatePresence(final String resource, final Presence presence) {
+        this.presences.updatePresence(resource, presence);
+    }
+
+    public void removePresence(final String resource) {
+        this.presences.removePresence(resource);
+    }
+
+    public void clearPresences() {
+        this.presences.clearPresences();
+        this.resetOption(Options.PENDING_SUBSCRIPTION_REQUEST);
+    }
+
+    public Presence.Status getShownStatus() {
+        return this.presences.getShownStatus();
+    }
+
+    public boolean setPhotoUri(String uri) {
+        if (uri != null && !uri.equals(this.photoUri)) {
+            this.photoUri = uri;
+            return true;
+        } else if (this.photoUri != null && uri == null) {
+            this.photoUri = null;
+            return true;
+        } else {
+            return false;
+        }
+    }
+
+    public void setServerName(String serverName) {
+        this.serverName = serverName;
+    }
+
+    public boolean setSystemName(String systemName) {
+        final String old = getDisplayName();
+        this.systemName = systemName;
+        return !old.equals(getDisplayName());
+    }
+
+    public boolean setPresenceName(String presenceName) {
+        final String old = getDisplayName();
+        this.presenceName = presenceName;
+        return !old.equals(getDisplayName());
+    }
+
+    public Uri getSystemAccount() {
+        return systemAccount;
+    }
+
+    public void setSystemAccount(Uri lookupUri) {
+        this.systemAccount = lookupUri;
+    }
+
+    private Collection<String> getGroups(final boolean unique) {
+        final Collection<String> groups = unique ? new HashSet<>() : new ArrayList<>();
+        for (int i = 0; i < this.groups.length(); ++i) {
+            try {
+                groups.add(this.groups.getString(i));
+            } catch (final JSONException ignored) {
+            }
+        }
+        return groups;
+    }
+
+    public long getPgpKeyId() {
+        synchronized (this.keys) {
+            if (this.keys.has("pgp_keyid")) {
+                try {
+                    return this.keys.getLong("pgp_keyid");
+                } catch (JSONException e) {
+                    return 0;
+                }
+            } else {
+                return 0;
+            }
+        }
+    }
+
+    public boolean setPgpKeyId(long keyId) {
+        final long previousKeyId = getPgpKeyId();
+        synchronized (this.keys) {
+            try {
+                this.keys.put("pgp_keyid", keyId);
+                return previousKeyId != keyId;
+            } catch (final JSONException ignored) {
+            }
+        }
+        return false;
+    }
+
+    public void setOption(int option) {
+        this.subscription |= 1 << option;
+    }
+
+    public void resetOption(int option) {
+        this.subscription &= ~(1 << option);
+    }
+
+    public boolean getOption(int option) {
+        return ((this.subscription & (1 << option)) != 0);
+    }
+
+    public boolean showInRoster() {
+        return (this.getOption(Contact.Options.IN_ROSTER) && (!this
+                .getOption(Contact.Options.DIRTY_DELETE)))
+                || (this.getOption(Contact.Options.DIRTY_PUSH));
+    }
+
+    public boolean showInContactList() {
+        return showInRoster()
+                || getOption(Options.SYNCED_VIA_OTHER)
+                || (QuickConversationsService.isQuicksy() && systemAccount != null);
+    }
+
+    public void parseSubscriptionFromElement(Element item) {
+        String ask = item.getAttribute("ask");
+        String subscription = item.getAttribute("subscription");
+
+        if (subscription == null) {
+            this.resetOption(Options.FROM);
+            this.resetOption(Options.TO);
+        } else {
+            switch (subscription) {
+                case "to":
+                    this.resetOption(Options.FROM);
+                    this.setOption(Options.TO);
+                    break;
+                case "from":
+                    this.resetOption(Options.TO);
+                    this.setOption(Options.FROM);
+                    this.resetOption(Options.PREEMPTIVE_GRANT);
+                    this.resetOption(Options.PENDING_SUBSCRIPTION_REQUEST);
+                    break;
+                case "both":
+                    this.setOption(Options.TO);
+                    this.setOption(Options.FROM);
+                    this.resetOption(Options.PREEMPTIVE_GRANT);
+                    this.resetOption(Options.PENDING_SUBSCRIPTION_REQUEST);
+                    break;
+                case "none":
+                    this.resetOption(Options.FROM);
+                    this.resetOption(Options.TO);
+                    break;
+            }
+        }
+
+        // do NOT override asking if pending push request
+        if (!this.getOption(Contact.Options.DIRTY_PUSH)) {
+            if ((ask != null) && (ask.equals("subscribe"))) {
+                this.setOption(Contact.Options.ASKING);
+            } else {
+                this.resetOption(Contact.Options.ASKING);
+            }
+        }
+    }
+
+    public void parseGroupsFromElement(Element item) {
+        this.groups = new JSONArray();
+        for (Element element : item.getChildren()) {
+            if (element.getName().equals("group") && element.getContent() != null) {
+                this.groups.put(element.getContent());
+            }
+        }
+    }
+
+    public Element asElement() {
+        final Element item = new Element("item");
+        item.setAttribute("jid", this.jid);
+        if (this.serverName != null) {
+            item.setAttribute("name", this.serverName);
+        }
+        for (String group : getGroups(false)) {
+            item.addChild("group").setContent(group);
+        }
+        return item;
+    }
+
+    @Override
+    public int compareTo(@NonNull final ListItem another) {
+        return this.getDisplayName().compareToIgnoreCase(
+                another.getDisplayName());
+    }
+
+    public String getServer() {
+        return getJid().getDomain().toEscapedString();
+    }
+
+    public void setAvatar(Avatar avatar) {
+        setAvatar(avatar, false);
+    }
+
+    public void setAvatar(Avatar avatar, boolean previouslyOmittedPepFetch) {
+        if (this.avatar != null && this.avatar.equals(avatar)) {
+            return;
+        }
+        if (!previouslyOmittedPepFetch && this.avatar != null && this.avatar.origin == Avatar.Origin.PEP && avatar.origin == Avatar.Origin.VCARD) {
+            return;
+        }
+        this.avatar = avatar;
+    }
+
+    public String getAvatarFilename() {
+        return avatar == null ? null : avatar.getFilename();
+    }
+
+    public Avatar getAvatar() {
+        return avatar;
+    }
+
+    public boolean mutualPresenceSubscription() {
+        return getOption(Options.FROM) && getOption(Options.TO);
+    }
+
+    @Override
+    public boolean isBlocked() {
+        return getAccount().isBlocked(this);
+    }
+
+    @Override
+    public boolean isDomainBlocked() {
+        return getAccount().isBlocked(this.getJid().getDomain());
+    }
+
+    @Override
+    public Jid getBlockedJid() {
+        if (isDomainBlocked()) {
+            return getJid().getDomain();
+        } else {
+            return getJid();
+        }
+    }
+
+    public boolean isSelf() {
+        return account.getJid().asBareJid().equals(jid.asBareJid());
+    }
+
+    boolean isOwnServer() {
+        return account.getJid().getDomain().equals(jid.asBareJid());
+    }
+
+    public void setCommonName(String cn) {
+        this.commonName = cn;
+    }
+
+    public void flagActive() {
+        this.mActive = true;
+    }
+
+    public void flagInactive() {
+        this.mActive = false;
+    }
+
+    public boolean isActive() {
+        return this.mActive;
+    }
+
+    public boolean setLastseen(long timestamp) {
+        if (timestamp > this.mLastseen) {
+            this.mLastseen = timestamp;
+            return true;
+        } else {
+            return false;
+        }
+    }
+
+    public long getLastseen() {
+        return this.mLastseen;
+    }
+
+    public void setLastResource(String resource) {
+        this.mLastPresence = resource;
+    }
+
+    public String getLastResource() {
+        return this.mLastPresence;
+    }
+
+    public String getServerName() {
+        return serverName;
+    }
+
+    public synchronized boolean setPhoneContact(AbstractPhoneContact phoneContact) {
+        setOption(getOption(phoneContact.getClass()));
+        setSystemAccount(phoneContact.getLookupUri());
+        boolean changed = setSystemName(phoneContact.getDisplayName());
+        changed |= setPhotoUri(phoneContact.getPhotoUri());
+        return changed;
+    }
+
+    public synchronized boolean unsetPhoneContact(Class<? extends AbstractPhoneContact> clazz) {
+        resetOption(getOption(clazz));
+        boolean changed = false;
+        if (!getOption(Options.SYNCED_VIA_ADDRESSBOOK) && !getOption(Options.SYNCED_VIA_OTHER)) {
+            setSystemAccount(null);
+            changed |= setPhotoUri(null);
+            changed |= setSystemName(null);
+        }
+        return changed;
+    }
+
+    public static int getOption(Class<? extends AbstractPhoneContact> clazz) {
+        if (clazz == JabberIdContact.class) {
+            return Options.SYNCED_VIA_ADDRESSBOOK;
+        } else {
+            return Options.SYNCED_VIA_OTHER;
+        }
+    }
+
+    @Override
+    public int getAvatarBackgroundColor() {
+        return UIHelper.getColorForName(jid != null ? jid.asBareJid().toString() : getDisplayName());
+    }
+
+    @Override
+    public String getAvatarName() {
+        return getDisplayName();
+    }
+
+    public boolean hasAvatarOrPresenceName() {
+        return (avatar != null && avatar.getFilename() != null) || presenceName != null;
+    }
+
+    public final class Options {
+        public static final int TO = 0;
+        public static final int FROM = 1;
+        public static final int ASKING = 2;
+        public static final int PREEMPTIVE_GRANT = 3;
+        public static final int IN_ROSTER = 4;
+        public static final int PENDING_SUBSCRIPTION_REQUEST = 5;
+        public static final int DIRTY_PUSH = 6;
+        public static final int DIRTY_DELETE = 7;
+        private static final int SYNCED_VIA_ADDRESSBOOK = 8;
+        public static final int SYNCED_VIA_OTHER = 9;
+    }
 }

src/main/java/eu/siacs/conversations/parser/MessageParser.java 🔗

@@ -214,13 +214,12 @@ public class MessageParser extends AbstractParser implements OnMessagePacketRece
                         mXmppConnectionService.updateConversationUi();
                         mXmppConnectionService.updateAccountUi();
                     } else {
-                        Contact contact = account.getRoster().getContact(from);
-                        if (contact.setAvatar(avatar)) {
-                            mXmppConnectionService.syncRoster(account);
-                            mXmppConnectionService.getAvatarService().clear(contact);
-                            mXmppConnectionService.updateConversationUi();
-                            mXmppConnectionService.updateRosterUi();
-                        }
+                        final Contact contact = account.getRoster().getContact(from);
+                        contact.setAvatar(avatar);
+                        mXmppConnectionService.syncRoster(account);
+                        mXmppConnectionService.getAvatarService().clear(contact);
+                        mXmppConnectionService.updateConversationUi();
+                        mXmppConnectionService.updateRosterUi();
                     }
                 } else if (mXmppConnectionService.isDataSaverDisabled()) {
                     mXmppConnectionService.fetchAvatar(account, avatar);

src/main/java/eu/siacs/conversations/parser/PresenceParser.java 🔗

@@ -118,12 +118,11 @@ public class PresenceParser extends AbstractParser implements
 									mXmppConnectionService.getAvatarService().clear(user);
 								}
 								if (user.getRealJid() != null) {
-									Contact c = conversation.getAccount().getRoster().getContact(user.getRealJid());
-									if (c.setAvatar(avatar)) {
-										mXmppConnectionService.syncRoster(conversation.getAccount());
-										mXmppConnectionService.getAvatarService().clear(c);
-										mXmppConnectionService.updateRosterUi();
-									}
+									final Contact c = conversation.getAccount().getRoster().getContact(user.getRealJid());
+									c.setAvatar(avatar);
+									mXmppConnectionService.syncRoster(conversation.getAccount());
+									mXmppConnectionService.getAvatarService().clear(c);
+									mXmppConnectionService.updateRosterUi();
 								}
 							} else if (mXmppConnectionService.isDataSaverDisabled()) {
 								mXmppConnectionService.fetchAvatar(mucOptions.getAccount(), avatar);
@@ -268,7 +267,8 @@ public class PresenceParser extends AbstractParser implements
 						mXmppConnectionService.getAvatarService().clear(account);
 						mXmppConnectionService.updateConversationUi();
 						mXmppConnectionService.updateAccountUi();
-					} else if (contact.setAvatar(avatar)) {
+					} else {
+						contact.setAvatar(avatar);
 						mXmppConnectionService.syncRoster(account);
 						mXmppConnectionService.getAvatarService().clear(contact);
 						mXmppConnectionService.updateConversationUi();

src/main/java/eu/siacs/conversations/persistance/FileBackend.java 🔗

@@ -1071,7 +1071,7 @@ public class FileBackend {
             return null;
         }
         Avatar avatar = new Avatar();
-        File file = new File(getAvatarPath(hash));
+        final File file = getAvatarFile(hash);
         FileInputStream is = null;
         try {
             avatar.size = file.length();
@@ -1104,14 +1104,14 @@ public class FileBackend {
     }
 
     public boolean isAvatarCached(Avatar avatar) {
-        File file = new File(getAvatarPath(avatar.getFilename()));
+        final File file = getAvatarFile(avatar.getFilename());
         return file.exists();
     }
 
     public boolean save(final Avatar avatar) {
         File file;
         if (isAvatarCached(avatar)) {
-            file = new File(getAvatarPath(avatar.getFilename()));
+            file = getAvatarFile(avatar.getFilename());
             avatar.size = file.length();
         } else {
             file = new File(mXmppConnectionService.getCacheDir().getAbsolutePath() + "/" + UUID.randomUUID().toString());
@@ -1133,12 +1133,12 @@ public class FileBackend {
                 mDigestOutputStream.close();
                 String sha1sum = CryptoHelper.bytesToHex(digest.digest());
                 if (sha1sum.equals(avatar.sha1sum)) {
-                    File outputFile = new File(getAvatarPath(avatar.getFilename()));
+                    final File outputFile = getAvatarFile(avatar.getFilename());
                     if (outputFile.getParentFile().mkdirs()) {
                         Log.d(Config.LOGTAG, "created avatar directory");
                     }
-                    String filename = getAvatarPath(avatar.getFilename());
-                    if (!file.renameTo(new File(filename))) {
+                    final File avatarFile = getAvatarFile(avatar.getFilename());
+                    if (!file.renameTo(avatarFile)) {
                         Log.d(Config.LOGTAG, "unable to rename " + file.getAbsolutePath() + " to " + outputFile);
                         return false;
                     }
@@ -1159,12 +1159,34 @@ public class FileBackend {
         return true;
     }
 
-    private String getAvatarPath(String avatar) {
-        return mXmppConnectionService.getFilesDir().getAbsolutePath() + "/avatars/" + avatar;
+    public void deleteHistoricAvatarPath() {
+        delete(getHistoricAvatarPath());
+    }
+
+    private void delete(final File file) {
+        if (file.isDirectory()) {
+            final File[] files = file.listFiles();
+            if (files != null) {
+                for (final File f : files) {
+                    delete(f);
+                }
+            }
+        }
+        if (file.delete()) {
+            Log.d(Config.LOGTAG,"deleted "+file.getAbsolutePath());
+        }
+    }
+
+    private File getHistoricAvatarPath() {
+        return new File(mXmppConnectionService.getFilesDir(), "/avatars/");
+    }
+
+    private File getAvatarFile(String avatar) {
+        return new File(mXmppConnectionService.getCacheDir(), "/avatars/" + avatar);
     }
 
     public Uri getAvatarUri(String avatar) {
-        return Uri.parse("file:" + getAvatarPath(avatar));
+        return Uri.fromFile(getAvatarFile(avatar));
     }
 
     public Bitmap cropCenterSquare(Uri image, int size) {

src/main/java/eu/siacs/conversations/services/AvatarService.java 🔗

@@ -59,7 +59,7 @@ public class AvatarService implements OnAdvancedStreamFeaturesLoaded {
 
 	private static final String CHANNEL_SYMBOL = "#";
 
-	final private ArrayList<Integer> sizes = new ArrayList<>();
+	final private Set<Integer> sizes = new HashSet<>();
 	final private HashMap<String, Set<String>> conversationDependentKeys = new HashMap<>();
 
 	protected XmppConnectionService mXmppConnectionService = null;
@@ -224,9 +224,8 @@ public class AvatarService implements OnAdvancedStreamFeaturesLoaded {
 
 	public void clear(Contact contact) {
 		synchronized (this.sizes) {
-			for (Integer size : sizes) {
-				this.mXmppConnectionService.getBitmapCache().remove(
-						key(contact, size));
+			for (final Integer size : sizes) {
+				this.mXmppConnectionService.getBitmapCache().remove(key(contact, size));
 			}
 		}
 		for (Conversation conversation : mXmppConnectionService.findAllConferencesWith(contact)) {
@@ -240,9 +239,7 @@ public class AvatarService implements OnAdvancedStreamFeaturesLoaded {
 
 	private String key(Contact contact, int size) {
 		synchronized (this.sizes) {
-			if (!this.sizes.contains(size)) {
-				this.sizes.add(size);
-			}
+			this.sizes.add(size);
 		}
 		return PREFIX_CONTACT +
 				'\0' +
@@ -255,9 +252,7 @@ public class AvatarService implements OnAdvancedStreamFeaturesLoaded {
 
 	private String key(MucOptions.User user, int size) {
 		synchronized (this.sizes) {
-			if (!this.sizes.contains(size)) {
-				this.sizes.add(size);
-			}
+			this.sizes.add(size);
 		}
 		return PREFIX_CONTACT +
 				'\0' +
@@ -416,12 +411,9 @@ public class AvatarService implements OnAdvancedStreamFeaturesLoaded {
 
 	private String key(final MucOptions options, int size) {
 		synchronized (this.sizes) {
-			if (!this.sizes.contains(size)) {
-				this.sizes.add(size);
-			}
+			this.sizes.add(size);
 		}
-		return PREFIX_CONVERSATION + "_" + options.getConversation().getUuid()
-				+ "_" + String.valueOf(size);
+		return PREFIX_CONVERSATION + "_" + options.getConversation().getUuid() + "_" + size;
 	}
 
 	private String key(List<MucOptions.User> users, int size) {
@@ -524,9 +516,7 @@ public class AvatarService implements OnAdvancedStreamFeaturesLoaded {
 
 	private String key(Account account, int size) {
 		synchronized (this.sizes) {
-			if (!this.sizes.contains(size)) {
-				this.sizes.add(size);
-			}
+			this.sizes.add(size);
 		}
 		return PREFIX_ACCOUNT + "_" + account.getUuid() + "_"
 				+ String.valueOf(size);
@@ -561,11 +551,9 @@ public class AvatarService implements OnAdvancedStreamFeaturesLoaded {
 
 	private String key(String name, int size) {
 		synchronized (this.sizes) {
-			if (!this.sizes.contains(size)) {
-				this.sizes.add(size);
-			}
+			this.sizes.add(size);
 		}
-		return PREFIX_GENERIC + "_" + name + "_" + String.valueOf(size);
+		return PREFIX_GENERIC + "_" + name + "_" + size;
 	}
 
 	private static boolean drawTile(Canvas canvas, String letter, int tileColor, int left, int top, int right, int bottom) {

src/main/java/eu/siacs/conversations/services/XmppConnectionService.java 🔗

@@ -1127,6 +1127,7 @@ public class XmppConnectionService extends Service {
         if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M || ContextCompat.checkSelfPermission(this, Manifest.permission.READ_CONTACTS) == PackageManager.PERMISSION_GRANTED) {
             startContactObserver();
         }
+        mFileAddingExecutor.execute(fileBackend::deleteHistoricAvatarPath);
         if (Compatibility.hasStoragePermission(this)) {
             Log.d(Config.LOGTAG, "starting file observer");
             mFileAddingExecutor.execute(this.fileObserver::startWatching);
@@ -3696,19 +3697,17 @@ public class XmppConnectionService extends Service {
                             updateConversationUi();
                             updateAccountUi();
                         } else {
-                            Contact contact = a.getRoster().getContact(avatar.owner);
-                            if (contact.setAvatar(avatar)) {
-                                syncRoster(account);
-                                getAvatarService().clear(contact);
-                                updateConversationUi();
-                                updateRosterUi();
-                            }
+                            final Contact contact = a.getRoster().getContact(avatar.owner);
+                            contact.setAvatar(avatar);
+                            syncRoster(account);
+                            getAvatarService().clear(contact);
+                            updateConversationUi();
+                            updateRosterUi();
                         }
                         if (callback != null) {
                             callback.success(avatar);
                         }
-                        Log.d(Config.LOGTAG, a.getJid().asBareJid()
-                                + ": successfully fetched pep avatar for " + avatar.owner);
+                        Log.d(Config.LOGTAG, a.getJid().asBareJid() + ": successfully fetched pep avatar for " + avatar.owner);
                         return;
                     }
                 } else {
@@ -3758,12 +3757,11 @@ public class XmppConnectionService extends Service {
                                     getAvatarService().clear(account);
                                     updateAccountUi();
                                 } else {
-                                    Contact contact = account.getRoster().getContact(avatar.owner);
-                                    if (contact.setAvatar(avatar, previouslyOmittedPepFetch)) {
-                                        syncRoster(account);
-                                        getAvatarService().clear(contact);
-                                        updateRosterUi();
-                                    }
+                                    final Contact contact = account.getRoster().getContact(avatar.owner);
+                                    contact.setAvatar(avatar, previouslyOmittedPepFetch);
+                                    syncRoster(account);
+                                    getAvatarService().clear(contact);
+                                    updateRosterUi();
                                 }
                                 updateConversationUi();
                             } else {
@@ -3778,11 +3776,10 @@ public class XmppConnectionService extends Service {
                                         }
                                         if (user.getRealJid() != null) {
                                             Contact contact = account.getRoster().getContact(user.getRealJid());
-                                            if (contact.setAvatar(avatar)) {
-                                                syncRoster(account);
-                                                getAvatarService().clear(contact);
-                                                updateRosterUi();
-                                            }
+                                            contact.setAvatar(avatar);
+                                            syncRoster(account);
+                                            getAvatarService().clear(contact);
+                                            updateRosterUi();
                                         }
                                     }
                                 }