MucOptions.java

  1package eu.siacs.conversations.entities;
  2
  3import java.util.ArrayList;
  4import java.util.List;
  5import java.util.concurrent.CopyOnWriteArrayList;
  6
  7import eu.siacs.conversations.Config;
  8import eu.siacs.conversations.R;
  9import eu.siacs.conversations.crypto.PgpEngine;
 10import eu.siacs.conversations.xml.Element;
 11import eu.siacs.conversations.xmpp.jid.InvalidJidException;
 12import eu.siacs.conversations.xmpp.jid.Jid;
 13import eu.siacs.conversations.xmpp.stanzas.PresencePacket;
 14
 15import android.annotation.SuppressLint;
 16
 17@SuppressLint("DefaultLocale")
 18public class MucOptions {
 19
 20	public enum Affiliation {
 21		OWNER("owner", 4, R.string.owner),
 22		ADMIN("admin", 3, R.string.admin),
 23		MEMBER("member", 2, R.string.member),
 24		OUTCAST("outcast", 0, R.string.outcast),
 25		NONE("none", 1, R.string.no_affiliation);
 26
 27		private Affiliation(String string, int rank, int resId) {
 28			this.string = string;
 29			this.resId = resId;
 30			this.rank = rank;
 31		}
 32
 33		private String string;
 34		private int resId;
 35		private int rank;
 36
 37		public int getResId() {
 38			return resId;
 39		}
 40
 41		@Override
 42		public String toString() {
 43			return this.string;
 44		}
 45
 46		public boolean outranks(Affiliation affiliation) {
 47			return rank > affiliation.rank;
 48		}
 49
 50		public boolean ranks(Affiliation affiliation) {
 51			return rank >= affiliation.rank;
 52		}
 53	}
 54
 55	public enum Role {
 56		MODERATOR("moderator", R.string.moderator),
 57		VISITOR("visitor", R.string.visitor),
 58		PARTICIPANT("participant", R.string.participant),
 59		NONE("none", R.string.no_role);
 60
 61		private Role(String string, int resId) {
 62			this.string = string;
 63			this.resId = resId;
 64		}
 65
 66		private String string;
 67		private int resId;
 68
 69		public int getResId() {
 70			return resId;
 71		}
 72
 73		@Override
 74		public String toString() {
 75			return this.string;
 76		}
 77	}
 78
 79	public static final int ERROR_NO_ERROR = 0;
 80	public static final int ERROR_NICK_IN_USE = 1;
 81	public static final int ERROR_UNKNOWN = 2;
 82	public static final int ERROR_PASSWORD_REQUIRED = 3;
 83	public static final int ERROR_BANNED = 4;
 84	public static final int ERROR_MEMBERS_ONLY = 5;
 85
 86	public static final int KICKED_FROM_ROOM = 9;
 87
 88	public static final String STATUS_CODE_ROOM_CONFIG_CHANGED = "104";
 89	public static final String STATUS_CODE_SELF_PRESENCE = "110";
 90	public static final String STATUS_CODE_BANNED = "301";
 91	public static final String STATUS_CODE_CHANGED_NICK = "303";
 92	public static final String STATUS_CODE_KICKED = "307";
 93	public static final String STATUS_CODE_LOST_MEMBERSHIP = "321";
 94
 95	private interface OnEventListener {
 96		public void onSuccess();
 97
 98		public void onFailure();
 99	}
100
101	public interface OnRenameListener extends OnEventListener {
102
103	}
104
105	public interface OnJoinListener extends OnEventListener {
106
107	}
108
109	public class User {
110		private Role role = Role.NONE;
111		private Affiliation affiliation = Affiliation.NONE;
112		private String name;
113		private Jid jid;
114		private long pgpKeyId = 0;
115
116		public String getName() {
117			return name;
118		}
119
120		public void setName(String user) {
121			this.name = user;
122		}
123
124		public void setJid(Jid jid) {
125			this.jid = jid;
126		}
127
128		public Jid getJid() {
129			return this.jid;
130		}
131
132		public Role getRole() {
133			return this.role;
134		}
135
136		public void setRole(String role) {
137			role = role.toLowerCase();
138			switch (role) {
139				case "moderator":
140					this.role = Role.MODERATOR;
141					break;
142				case "participant":
143					this.role = Role.PARTICIPANT;
144					break;
145				case "visitor":
146					this.role = Role.VISITOR;
147					break;
148				default:
149					this.role = Role.NONE;
150					break;
151			}
152		}
153
154		public Affiliation getAffiliation() {
155			return this.affiliation;
156		}
157
158		public void setAffiliation(String affiliation) {
159			affiliation = affiliation.toLowerCase();
160			switch (affiliation) {
161				case "admin":
162					this.affiliation = Affiliation.ADMIN;
163					break;
164				case "owner":
165					this.affiliation = Affiliation.OWNER;
166					break;
167				case "member":
168					this.affiliation = Affiliation.MEMBER;
169					break;
170				case "outcast":
171					this.affiliation = Affiliation.OUTCAST;
172					break;
173				default:
174					this.affiliation = Affiliation.NONE;
175			}
176		}
177
178		public void setPgpKeyId(long id) {
179			this.pgpKeyId = id;
180		}
181
182		public long getPgpKeyId() {
183			return this.pgpKeyId;
184		}
185
186		public Contact getContact() {
187			return account.getRoster().getContactFromRoster(getJid());
188		}
189	}
190
191	private Account account;
192	private List<User> users = new CopyOnWriteArrayList<>();
193	private List<String> features = new ArrayList<>();
194	private Conversation conversation;
195	private boolean isOnline = false;
196	private int error = ERROR_UNKNOWN;
197	private OnRenameListener onRenameListener = null;
198	private OnJoinListener onJoinListener = null;
199	private User self = new User();
200	private String subject = null;
201	private String password = null;
202	private boolean mNickChangingInProgress = false;
203
204	public MucOptions(Conversation conversation) {
205		this.account = conversation.getAccount();
206		this.conversation = conversation;
207	}
208
209	public void updateFeatures(ArrayList<String> features) {
210		this.features.clear();
211		this.features.addAll(features);
212	}
213
214	public boolean hasFeature(String feature) {
215		return this.features.contains(feature);
216	}
217
218	public boolean canInvite() {
219		 return !membersOnly() || self.getAffiliation().ranks(Affiliation.ADMIN);
220	}
221
222	public boolean membersOnly() {
223		return hasFeature("muc_membersonly");
224	}
225
226	public boolean nonanonymous() {
227		return hasFeature("muc_nonanonymous");
228	}
229
230	public void deleteUser(String name) {
231		for (int i = 0; i < users.size(); ++i) {
232			if (users.get(i).getName().equals(name)) {
233				users.remove(i);
234				return;
235			}
236		}
237	}
238
239	public void addUser(User user) {
240		for (int i = 0; i < users.size(); ++i) {
241			if (users.get(i).getName().equals(user.getName())) {
242				users.set(i, user);
243				return;
244			}
245		}
246		users.add(user);
247	}
248
249	public void processPacket(PresencePacket packet, PgpEngine pgp) {
250		final Jid from = packet.getFrom();
251		if (!from.isBareJid()) {
252			final String name = from.getResourcepart();
253			final String type = packet.getAttribute("type");
254			final Element x = packet.findChild("x", "http://jabber.org/protocol/muc#user");
255			final List<String> codes = getStatusCodes(x);
256			if (type == null) {
257				User user = new User();
258				if (x != null) {
259					Element item = x.findChild("item");
260					if (item != null) {
261						user.setName(name);
262						user.setAffiliation(item.getAttribute("affiliation"));
263						user.setRole(item.getAttribute("role"));
264						user.setJid(item.getAttributeAsJid("jid"));
265						if (codes.contains(STATUS_CODE_SELF_PRESENCE) || packet.getFrom().equals(this.conversation.getJid())) {
266							this.isOnline = true;
267							this.error = ERROR_NO_ERROR;
268							self = user;
269							if (mNickChangingInProgress) {
270								onRenameListener.onSuccess();
271								mNickChangingInProgress = false;
272							} else if (this.onJoinListener != null) {
273								this.onJoinListener.onSuccess();
274								this.onJoinListener = null;
275							}
276						} else {
277							addUser(user);
278						}
279						if (pgp != null) {
280							Element signed = packet.findChild("x", "jabber:x:signed");
281							if (signed != null) {
282								Element status = packet.findChild("status");
283								String msg;
284								if (status != null) {
285									msg = status.getContent();
286								} else {
287									msg = "";
288								}
289								user.setPgpKeyId(pgp.fetchKeyId(account, msg,
290										signed.getContent()));
291							}
292						}
293					}
294				}
295			} else if (type.equals("unavailable")) {
296				if (codes.contains(STATUS_CODE_SELF_PRESENCE) ||
297						packet.getFrom().equals(this.conversation.getJid())) {
298					if (codes.contains(STATUS_CODE_CHANGED_NICK)) {
299						this.mNickChangingInProgress = true;
300					} else if (codes.contains(STATUS_CODE_KICKED)) {
301						setError(KICKED_FROM_ROOM);
302					} else if (codes.contains(STATUS_CODE_BANNED)) {
303						setError(ERROR_BANNED);
304					} else if (codes.contains(STATUS_CODE_LOST_MEMBERSHIP)) {
305						setError(ERROR_MEMBERS_ONLY);
306					} else {
307						setError(ERROR_UNKNOWN);
308					}
309				} else {
310					deleteUser(name);
311				}
312			} else if (type.equals("error")) {
313				Element error = packet.findChild("error");
314				if (error != null && error.hasChild("conflict")) {
315					if (isOnline) {
316						if (onRenameListener != null) {
317							onRenameListener.onFailure();
318						}
319					} else {
320						setError(ERROR_NICK_IN_USE);
321					}
322				} else if (error != null && error.hasChild("not-authorized")) {
323					setError(ERROR_PASSWORD_REQUIRED);
324				} else if (error != null && error.hasChild("forbidden")) {
325					setError(ERROR_BANNED);
326				} else if (error != null && error.hasChild("registration-required")) {
327					setError(ERROR_MEMBERS_ONLY);
328				} else {
329					setError(ERROR_UNKNOWN);
330				}
331			}
332		}
333	}
334
335	private void setError(int error) {
336		this.isOnline = false;
337		this.error = error;
338		if (onJoinListener != null) {
339			onJoinListener.onFailure();
340			onJoinListener = null;
341		}
342	}
343
344	private List<String> getStatusCodes(Element x) {
345		List<String> codes = new ArrayList<>();
346		if (x != null) {
347			for (Element child : x.getChildren()) {
348				if (child.getName().equals("status")) {
349					String code = child.getAttribute("code");
350					if (code != null) {
351						codes.add(code);
352					}
353				}
354			}
355		}
356		return codes;
357	}
358
359	public List<User> getUsers() {
360		return this.users;
361	}
362
363	public String getProposedNick() {
364		if (conversation.getBookmark() != null
365				&& conversation.getBookmark().getNick() != null
366				&& !conversation.getBookmark().getNick().isEmpty()) {
367			return conversation.getBookmark().getNick();
368		} else if (!conversation.getJid().isBareJid()) {
369			return conversation.getJid().getResourcepart();
370		} else {
371			return account.getUsername();
372		}
373	}
374
375	public String getActualNick() {
376		if (this.self.getName() != null) {
377			return this.self.getName();
378		} else {
379			return this.getProposedNick();
380		}
381	}
382
383	public boolean online() {
384		return this.isOnline;
385	}
386
387	public int getError() {
388		return this.error;
389	}
390
391	public void setOnRenameListener(OnRenameListener listener) {
392		this.onRenameListener = listener;
393	}
394
395	public void setOnJoinListener(OnJoinListener listener) {
396		this.onJoinListener = listener;
397	}
398
399	public void setOffline() {
400		this.users.clear();
401		this.error = 0;
402		this.isOnline = false;
403	}
404
405	public User getSelf() {
406		return self;
407	}
408
409	public void setSubject(String content) {
410		this.subject = content;
411	}
412
413	public String getSubject() {
414		return this.subject;
415	}
416
417	public String createNameFromParticipants() {
418		if (users.size() >= 2) {
419			List<String> names = new ArrayList<String>();
420			for (User user : users) {
421				Contact contact = user.getContact();
422				if (contact != null && !contact.getDisplayName().isEmpty()) {
423					names.add(contact.getDisplayName().split("\\s+")[0]);
424				} else {
425					names.add(user.getName());
426				}
427			}
428			StringBuilder builder = new StringBuilder();
429			for (int i = 0; i < names.size(); ++i) {
430				builder.append(names.get(i));
431				if (i != names.size() - 1) {
432					builder.append(", ");
433				}
434			}
435			return builder.toString();
436		} else {
437			return null;
438		}
439	}
440
441	public long[] getPgpKeyIds() {
442		List<Long> ids = new ArrayList<>();
443		for (User user : getUsers()) {
444			if (user.getPgpKeyId() != 0) {
445				ids.add(user.getPgpKeyId());
446			}
447		}
448		long[] primitivLongArray = new long[ids.size()];
449		for (int i = 0; i < ids.size(); ++i) {
450			primitivLongArray[i] = ids.get(i);
451		}
452		return primitivLongArray;
453	}
454
455	public boolean pgpKeysInUse() {
456		for (User user : getUsers()) {
457			if (user.getPgpKeyId() != 0) {
458				return true;
459			}
460		}
461		return false;
462	}
463
464	public boolean everybodyHasKeys() {
465		for (User user : getUsers()) {
466			if (user.getPgpKeyId() == 0) {
467				return false;
468			}
469		}
470		return true;
471	}
472
473	public Jid createJoinJid(String nick) {
474		try {
475			return Jid.fromString(this.conversation.getJid().toBareJid().toString() + "/" + nick);
476		} catch (final InvalidJidException e) {
477			return null;
478		}
479	}
480
481	public Jid getTrueCounterpart(String counterpart) {
482		for (User user : this.getUsers()) {
483			if (user.getName().equals(counterpart)) {
484				return user.getJid();
485			}
486		}
487		return null;
488	}
489
490	public String getPassword() {
491		this.password = conversation.getAttribute(Conversation.ATTRIBUTE_MUC_PASSWORD);
492		if (this.password == null && conversation.getBookmark() != null
493				&& conversation.getBookmark().getPassword() != null) {
494			return conversation.getBookmark().getPassword();
495		} else {
496			return this.password;
497		}
498	}
499
500	public void setPassword(String password) {
501		if (conversation.getBookmark() != null) {
502			conversation.getBookmark().setPassword(password);
503		} else {
504			this.password = password;
505		}
506		conversation.setAttribute(Conversation.ATTRIBUTE_MUC_PASSWORD, password);
507	}
508
509	public Conversation getConversation() {
510		return this.conversation;
511	}
512}