MucOptions.java

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