MucOptions.java

  1package eu.siacs.conversations.entities;
  2
  3import android.annotation.SuppressLint;
  4
  5import java.util.ArrayList;
  6import java.util.Collections;
  7import java.util.HashSet;
  8import java.util.List;
  9import java.util.Set;
 10
 11import eu.siacs.conversations.R;
 12import eu.siacs.conversations.xmpp.forms.Data;
 13import eu.siacs.conversations.xmpp.forms.Field;
 14import eu.siacs.conversations.xmpp.jid.InvalidJidException;
 15import eu.siacs.conversations.xmpp.jid.Jid;
 16import eu.siacs.conversations.xmpp.pep.Avatar;
 17
 18@SuppressLint("DefaultLocale")
 19public class MucOptions {
 20
 21	public Account getAccount() {
 22		return this.conversation.getAccount();
 23	}
 24
 25	public void setSelf(User user) {
 26		this.self = user;
 27	}
 28
 29	public void changeAffiliation(Jid jid, Affiliation affiliation) {
 30		User user = findUserByRealJid(jid);
 31		synchronized (users) {
 32			if (user != null && user.getRole() == Role.NONE) {
 33				users.remove(user);
 34				if (affiliation.ranks(Affiliation.MEMBER)) {
 35					user.affiliation = affiliation;
 36					users.add(user);
 37				}
 38			}
 39		}
 40	}
 41
 42	public enum Affiliation {
 43		OWNER("owner", 4, R.string.owner),
 44		ADMIN("admin", 3, R.string.admin),
 45		MEMBER("member", 2, R.string.member),
 46		OUTCAST("outcast", 0, R.string.outcast),
 47		NONE("none", 1, R.string.no_affiliation);
 48
 49		Affiliation(String string, int rank, int resId) {
 50			this.string = string;
 51			this.resId = resId;
 52			this.rank = rank;
 53		}
 54
 55		private String string;
 56		private int resId;
 57		private int rank;
 58
 59		public int getResId() {
 60			return resId;
 61		}
 62
 63		@Override
 64		public String toString() {
 65			return this.string;
 66		}
 67
 68		public boolean outranks(Affiliation affiliation) {
 69			return rank > affiliation.rank;
 70		}
 71
 72		public boolean ranks(Affiliation affiliation) {
 73			return rank >= affiliation.rank;
 74		}
 75	}
 76
 77	public enum Role {
 78		MODERATOR("moderator", R.string.moderator,3),
 79		VISITOR("visitor", R.string.visitor,1),
 80		PARTICIPANT("participant", R.string.participant,2),
 81		NONE("none", R.string.no_role,0);
 82
 83		Role(String string, int resId, int rank) {
 84			this.string = string;
 85			this.resId = resId;
 86			this.rank = rank;
 87		}
 88
 89		private String string;
 90		private int resId;
 91		private int rank;
 92
 93		public int getResId() {
 94			return resId;
 95		}
 96
 97		@Override
 98		public String toString() {
 99			return this.string;
100		}
101
102		public boolean ranks(Role role) {
103			return rank >= role.rank;
104		}
105	}
106
107	public enum Error {
108		NO_RESPONSE,
109		NONE,
110		NICK_IN_USE,
111		PASSWORD_REQUIRED,
112		BANNED,
113		MEMBERS_ONLY,
114		KICKED,
115		SHUTDOWN,
116		UNKNOWN
117	}
118
119	public static final String STATUS_CODE_ROOM_CONFIG_CHANGED = "104";
120	public static final String STATUS_CODE_SELF_PRESENCE = "110";
121	public static final String STATUS_CODE_BANNED = "301";
122	public static final String STATUS_CODE_CHANGED_NICK = "303";
123	public static final String STATUS_CODE_KICKED = "307";
124	public static final String STATUS_CODE_AFFILIATION_CHANGE = "321";
125	public static final String STATUS_CODE_LOST_MEMBERSHIP = "322";
126	public static final String STATUS_CODE_SHUTDOWN = "332";
127
128	private interface OnEventListener {
129		void onSuccess();
130
131		void onFailure();
132	}
133
134	public interface OnRenameListener extends OnEventListener {
135
136	}
137
138	public static class User implements Comparable<User> {
139		private Role role = Role.NONE;
140		private Affiliation affiliation = Affiliation.NONE;
141		private Jid realJid;
142		private Jid fullJid;
143		private long pgpKeyId = 0;
144		private Avatar avatar;
145		private MucOptions options;
146
147		public User(MucOptions options, Jid from) {
148			this.options = options;
149			this.fullJid = from;
150		}
151
152		public String getName() {
153			return fullJid == null ? null : fullJid.getResourcepart();
154		}
155
156		public void setRealJid(Jid jid) {
157			this.realJid = jid != null ? jid.toBareJid() : null;
158		}
159
160		public Role getRole() {
161			return this.role;
162		}
163
164		public void setRole(String role) {
165			if (role == null) {
166				this.role = Role.NONE;
167				return;
168			}
169			role = role.toLowerCase();
170			switch (role) {
171				case "moderator":
172					this.role = Role.MODERATOR;
173					break;
174				case "participant":
175					this.role = Role.PARTICIPANT;
176					break;
177				case "visitor":
178					this.role = Role.VISITOR;
179					break;
180				default:
181					this.role = Role.NONE;
182					break;
183			}
184		}
185
186		public Affiliation getAffiliation() {
187			return this.affiliation;
188		}
189
190		public void setAffiliation(String affiliation) {
191			if (affiliation == null) {
192				this.affiliation = Affiliation.NONE;
193				return;
194			}
195			affiliation = affiliation.toLowerCase();
196			switch (affiliation) {
197				case "admin":
198					this.affiliation = Affiliation.ADMIN;
199					break;
200				case "owner":
201					this.affiliation = Affiliation.OWNER;
202					break;
203				case "member":
204					this.affiliation = Affiliation.MEMBER;
205					break;
206				case "outcast":
207					this.affiliation = Affiliation.OUTCAST;
208					break;
209				default:
210					this.affiliation = Affiliation.NONE;
211			}
212		}
213
214		public void setPgpKeyId(long id) {
215			this.pgpKeyId = id;
216		}
217
218		public long getPgpKeyId() {
219			return this.pgpKeyId;
220		}
221
222		public Contact getContact() {
223			if (fullJid != null) {
224				return getAccount().getRoster().getContactFromRoster(realJid);
225			} else if (realJid != null){
226				return getAccount().getRoster().getContact(realJid);
227			} else {
228				return null;
229			}
230		}
231
232		public boolean setAvatar(Avatar avatar) {
233			if (this.avatar != null && this.avatar.equals(avatar)) {
234				return false;
235			} else {
236				this.avatar = avatar;
237				return true;
238			}
239		}
240
241		public String getAvatar() {
242			return avatar == null ? null : avatar.getFilename();
243		}
244
245		public Account getAccount() {
246			return options.getAccount();
247		}
248
249		public Jid getFullJid() {
250			return fullJid;
251		}
252
253		@Override
254		public boolean equals(Object o) {
255			if (this == o) return true;
256			if (o == null || getClass() != o.getClass()) return false;
257
258			User user = (User) o;
259
260			if (role != user.role) return false;
261			if (affiliation != user.affiliation) return false;
262			if (realJid != null ? !realJid.equals(user.realJid) : user.realJid != null)
263				return false;
264			return fullJid != null ? fullJid.equals(user.fullJid) : user.fullJid == null;
265
266		}
267
268		@Override
269		public int hashCode() {
270			int result = role != null ? role.hashCode() : 0;
271			result = 31 * result + (affiliation != null ? affiliation.hashCode() : 0);
272			result = 31 * result + (realJid != null ? realJid.hashCode() : 0);
273			result = 31 * result + (fullJid != null ? fullJid.hashCode() : 0);
274			return result;
275		}
276
277		@Override
278		public String toString() {
279			return "[fulljid:"+String.valueOf(fullJid)+",realjid:"+String.valueOf(realJid)+",affiliation"+affiliation.toString()+"]";
280		}
281
282		public boolean realJidMatchesAccount() {
283			return realJid != null && realJid.equals(options.account.getJid().toBareJid());
284		}
285
286		@Override
287		public int compareTo(User another) {
288			Contact ourContact = getContact();
289			Contact anotherContact = another.getContact();
290			if (ourContact != null && anotherContact != null) {
291				return ourContact.compareTo(anotherContact);
292			} else if (ourContact == null && anotherContact != null) {
293				return getName().compareToIgnoreCase(anotherContact.getDisplayName());
294			} else if (ourContact != null) {
295				return ourContact.getDisplayName().compareToIgnoreCase(another.getName());
296			} else {
297				return getName().compareToIgnoreCase(another.getName());
298			}
299		}
300
301		public Jid getRealJid() {
302			return realJid;
303		}
304	}
305
306	private Account account;
307	private final Set<User> users = new HashSet<>();
308	private final List<String> features = new ArrayList<>();
309	private Data form = new Data();
310	private Conversation conversation;
311	private boolean isOnline = false;
312	private Error error = Error.NONE;
313	public OnRenameListener onRenameListener = null;
314	private User self;
315	private String subject = null;
316	private String password = null;
317	public boolean mNickChangingInProgress = false;
318
319	public MucOptions(Conversation conversation) {
320		this.account = conversation.getAccount();
321		this.conversation = conversation;
322		this.self = new User(this,createJoinJid(getProposedNick()));
323	}
324
325	public void updateFeatures(ArrayList<String> features) {
326		this.features.clear();
327		this.features.addAll(features);
328	}
329
330	public void updateFormData(Data form) {
331		this.form = form;
332	}
333
334	public boolean hasFeature(String feature) {
335		return this.features.contains(feature);
336	}
337
338	public boolean canInvite() {
339		Field field = this.form.getFieldByName("muc#roomconfig_allowinvites");
340		return !membersOnly() || self.getRole().ranks(Role.MODERATOR) || (field != null && "1".equals(field.getValue()));
341	}
342
343	public boolean canChangeSubject() {
344		Field field = this.form.getFieldByName("muc#roomconfig_changesubject");
345		return self.getRole().ranks(Role.MODERATOR) || (field != null && "1".equals(field.getValue()));
346	}
347
348	public boolean participating() {
349		return !online()
350				|| self.getRole().ranks(Role.PARTICIPANT)
351				|| hasFeature("muc_unmoderated");
352	}
353
354	public boolean membersOnly() {
355		return hasFeature("muc_membersonly");
356	}
357
358	public boolean mamSupport() {
359		// Update with "urn:xmpp:mam:1" once we support it
360		return hasFeature("urn:xmpp:mam:0");
361	}
362
363	public boolean nonanonymous() {
364		return hasFeature("muc_nonanonymous");
365	}
366
367	public boolean persistent() {
368		return hasFeature("muc_persistent");
369	}
370
371	public boolean moderated() {
372		return hasFeature("muc_moderated");
373	}
374
375	public User deleteUser(Jid jid) {
376		User user = findUserByFullJid(jid);
377		if (user != null) {
378			synchronized (users) {
379				users.remove(user);
380				if (user.affiliation.ranks(Affiliation.MEMBER) && user.realJid != null) {
381					user.role = Role.NONE;
382					user.avatar = null;
383					user.fullJid = null;
384					users.add(user);
385				}
386			}
387		}
388		return user;
389	}
390
391	public void addUser(User user) {
392		User old;
393		if (user.fullJid == null && user.realJid != null) {
394			old = findUserByRealJid(user.realJid);
395			if (old != null) {
396				if (old.fullJid != null) {
397					return; //don't add. user already exists
398				} else {
399					synchronized (users) {
400						users.remove(old);
401					}
402				}
403			}
404		} else if (user.realJid != null) {
405			old = findUserByRealJid(user.realJid);
406			synchronized (users) {
407				if (old != null && old.fullJid == null) {
408					users.remove(old);
409				}
410			}
411		}
412		old = findUserByFullJid(user.getFullJid());
413		synchronized (this.users) {
414			if (old != null) {
415				users.remove(old);
416			}
417			this.users.add(user);
418		}
419	}
420
421	public User findUserByFullJid(Jid jid) {
422		if (jid == null) {
423			return null;
424		}
425		synchronized (users) {
426			for (User user : users) {
427				if (jid.equals(user.getFullJid())) {
428					return user;
429				}
430			}
431		}
432		return null;
433	}
434
435	public User findUserByRealJid(Jid jid) {
436		if (jid == null) {
437			return null;
438		}
439		synchronized (users) {
440			for (User user : users) {
441				if (jid.equals(user.realJid)) {
442					return user;
443				}
444			}
445		}
446		return null;
447	}
448
449	public boolean isUserInRoom(Jid jid) {
450		return findUserByFullJid(jid) != null;
451	}
452
453	public void setError(Error error) {
454		this.isOnline = isOnline && error == Error.NONE;
455		this.error = error;
456	}
457
458	public void setOnline() {
459		this.isOnline = true;
460	}
461
462	public ArrayList<User> getUsers() {
463		return getUsers(true);
464	}
465
466	public ArrayList<User> getUsers(boolean includeOffline) {
467		synchronized (users) {
468			if (includeOffline) {
469				return new ArrayList<>(users);
470			} else {
471				ArrayList<User> onlineUsers = new ArrayList<>();
472				for (User user : users) {
473					if (user.getRole().ranks(Role.PARTICIPANT)) {
474						onlineUsers.add(user);
475					}
476				}
477				return onlineUsers;
478			}
479		}
480	}
481
482	public List<User> getUsers(int max) {
483		ArrayList<User> users = getUsers();
484		return users.subList(0, Math.min(max, users.size()));
485	}
486
487	public int getUserCount() {
488		synchronized (users) {
489			return users.size();
490		}
491	}
492
493	public String getProposedNick() {
494		if (conversation.getBookmark() != null
495				&& conversation.getBookmark().getNick() != null
496				&& !conversation.getBookmark().getNick().isEmpty()) {
497			return conversation.getBookmark().getNick();
498		} else if (!conversation.getJid().isBareJid()) {
499			return conversation.getJid().getResourcepart();
500		} else {
501			return account.getUsername();
502		}
503	}
504
505	public String getActualNick() {
506		if (this.self.getName() != null) {
507			return this.self.getName();
508		} else {
509			return this.getProposedNick();
510		}
511	}
512
513	public boolean online() {
514		return this.isOnline;
515	}
516
517	public Error getError() {
518		return this.error;
519	}
520
521	public void setOnRenameListener(OnRenameListener listener) {
522		this.onRenameListener = listener;
523	}
524
525	public void setOffline() {
526		synchronized (users) {
527			this.users.clear();
528		}
529		this.error = Error.NO_RESPONSE;
530		this.isOnline = false;
531	}
532
533	public User getSelf() {
534		return self;
535	}
536
537	public void setSubject(String content) {
538		this.subject = content;
539	}
540
541	public String getSubject() {
542		return this.subject;
543	}
544
545	public String createNameFromParticipants() {
546		if (getUserCount() >= 2) {
547			List<String> names = new ArrayList<>();
548			for (User user : getUsers(5)) {
549				Contact contact = user.getContact();
550				if (contact != null && !contact.getDisplayName().isEmpty()) {
551					names.add(contact.getDisplayName().split("\\s+")[0]);
552				} else if (user.getName() != null){
553					names.add(user.getName());
554				}
555			}
556			StringBuilder builder = new StringBuilder();
557			for (int i = 0; i < names.size(); ++i) {
558				builder.append(names.get(i));
559				if (i != names.size() - 1) {
560					builder.append(", ");
561				}
562			}
563			return builder.toString();
564		} else {
565			return null;
566		}
567	}
568
569	public long[] getPgpKeyIds() {
570		List<Long> ids = new ArrayList<>();
571		for (User user : this.users) {
572			if (user.getPgpKeyId() != 0) {
573				ids.add(user.getPgpKeyId());
574			}
575		}
576		ids.add(account.getPgpId());
577		long[] primitiveLongArray = new long[ids.size()];
578		for (int i = 0; i < ids.size(); ++i) {
579			primitiveLongArray[i] = ids.get(i);
580		}
581		return primitiveLongArray;
582	}
583
584	public boolean pgpKeysInUse() {
585		synchronized (users) {
586			for (User user : users) {
587				if (user.getPgpKeyId() != 0) {
588					return true;
589				}
590			}
591		}
592		return false;
593	}
594
595	public boolean everybodyHasKeys() {
596		synchronized (users) {
597			for (User user : users) {
598				if (user.getPgpKeyId() == 0) {
599					return false;
600				}
601			}
602		}
603		return true;
604	}
605
606	public Jid createJoinJid(String nick) {
607		try {
608			return Jid.fromString(this.conversation.getJid().toBareJid().toString() + "/" + nick);
609		} catch (final InvalidJidException e) {
610			return null;
611		}
612	}
613
614	public Jid getTrueCounterpart(Jid jid) {
615		if (jid.equals(getSelf().getFullJid())) {
616			return account.getJid().toBareJid();
617		}
618		User user = findUserByFullJid(jid);
619		return user == null ? null : user.realJid;
620	}
621
622	public String getPassword() {
623		this.password = conversation.getAttribute(Conversation.ATTRIBUTE_MUC_PASSWORD);
624		if (this.password == null && conversation.getBookmark() != null
625				&& conversation.getBookmark().getPassword() != null) {
626			return conversation.getBookmark().getPassword();
627		} else {
628			return this.password;
629		}
630	}
631
632	public void setPassword(String password) {
633		if (conversation.getBookmark() != null) {
634			conversation.getBookmark().setPassword(password);
635		} else {
636			this.password = password;
637		}
638		conversation.setAttribute(Conversation.ATTRIBUTE_MUC_PASSWORD, password);
639	}
640
641	public Conversation getConversation() {
642		return this.conversation;
643	}
644
645	public List<Jid> getMembers() {
646		ArrayList<Jid> members = new ArrayList<>();
647		synchronized (users) {
648			for (User user : users) {
649				if (user.affiliation.ranks(Affiliation.MEMBER) && user.realJid != null) {
650					members.add(user.realJid);
651				}
652			}
653		}
654		return members;
655	}
656}