/** * Copyright (c) 2013, Redsolution LTD. All rights reserved. * * This file is part of Xabber project; you can redistribute it and/or * modify it under the terms of the GNU General Public License, Version 3. * * Xabber is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. * See the GNU General Public License for more details. * * You should have received a copy of the GNU General Public License, * along with this program. If not, see http://www.gnu.org/licenses/. */ package com.xabber.android.data.extension.muc; import android.database.Cursor; import com.xabber.android.R; import com.xabber.android.data.Application; import com.xabber.android.data.log.LogManager; import com.xabber.android.data.NetworkException; import com.xabber.android.data.OnLoadListener; import com.xabber.android.data.account.AccountItem; import com.xabber.android.data.account.AccountManager; import com.xabber.android.data.connection.ConnectionItem; import com.xabber.android.data.connection.StanzaSender; import com.xabber.android.data.connection.listeners.OnPacketListener; import com.xabber.android.data.database.sqlite.RoomTable; import com.xabber.android.data.entity.AccountJid; import com.xabber.android.data.entity.UserJid; import com.xabber.android.data.message.AbstractChat; import com.xabber.android.data.message.ChatAction; import com.xabber.android.data.message.MessageManager; import com.xabber.android.data.notification.EntityNotificationProvider; import com.xabber.android.data.notification.NotificationManager; import com.xabber.android.data.roster.RosterManager; import org.jivesoftware.smack.SmackException; import org.jivesoftware.smack.XMPPConnection; import org.jivesoftware.smack.XMPPException; import org.jivesoftware.smack.packet.Message; import org.jivesoftware.smack.packet.Stanza; import org.jivesoftware.smack.packet.XMPPError; import org.jivesoftware.smackx.muc.HostedRoom; import org.jivesoftware.smackx.muc.MultiUserChat; import org.jivesoftware.smackx.muc.MultiUserChatException; import org.jivesoftware.smackx.muc.MultiUserChatManager; import org.jivesoftware.smackx.muc.RoomInfo; import org.jivesoftware.smackx.muc.packet.MUCUser; import org.jxmpp.jid.DomainBareJid; import org.jxmpp.jid.EntityBareJid; import org.jxmpp.jid.Jid; import org.jxmpp.jid.impl.JidCreate; import org.jxmpp.jid.parts.Resourcepart; import org.jxmpp.stringprep.XmppStringprepException; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; /** * Manage multi user chats. * <p/> * Warning: We are going to remove SMACK components. * * @author alexander.ivanov */ public class MUCManager implements OnLoadListener, OnPacketListener { private static MUCManager instance; private final EntityNotificationProvider<RoomInvite> inviteProvider; private final EntityNotificationProvider<RoomAuthorizationError> authorizationErrorProvider; public static MUCManager getInstance() { if (instance == null) { instance = new MUCManager(); } return instance; } private MUCManager() { inviteProvider = new EntityNotificationProvider<>(R.drawable.ic_stat_add_circle); authorizationErrorProvider = new EntityNotificationProvider<>(R.drawable.ic_stat_error); } @Override public void onLoad() { Application.getInstance().runOnUiThread(new Runnable() { @Override public void run() { final Collection<RoomChat> roomChats = new ArrayList<>(); final Collection<RoomChat> needJoins = new ArrayList<>(); Cursor cursor = RoomTable.getInstance().list(); try { if (cursor.moveToFirst()) { do { try { Resourcepart nickName = Resourcepart.from(RoomTable.getNickname(cursor)); AccountJid account = AccountJid.from(RoomTable.getAccount(cursor)); EntityBareJid room = JidCreate.entityBareFrom(RoomTable.getRoom(cursor)); RoomChat roomChat = RoomChat.create(account, room, nickName, RoomTable.getPassword(cursor)); if (RoomTable.needJoin(cursor)) { needJoins.add(roomChat); } roomChats.add(roomChat); } catch (UserJid.UserJidCreateException | XmppStringprepException e) { e.printStackTrace(); } } while (cursor.moveToNext()); } } finally { cursor.close(); } onLoaded(roomChats, needJoins); } }); } private void onLoaded(Collection<RoomChat> roomChats, Collection<RoomChat> needJoins) { for (RoomChat roomChat : roomChats) { AbstractChat abstractChat = MessageManager.getInstance().getChat( roomChat.getAccount(), roomChat.getUser()); if (abstractChat != null) { MessageManager.getInstance().removeChat(abstractChat); } MessageManager.getInstance().addChat(roomChat); if (needJoins.contains(roomChat)) { roomChat.setState(RoomState.waiting); } } NotificationManager.getInstance().registerNotificationProvider(inviteProvider); NotificationManager.getInstance().registerNotificationProvider(authorizationErrorProvider); } /** * @return <code>null</code> if does not exists. */ public RoomChat getRoomChat(AccountJid account, EntityBareJid room) { AbstractChat chat; try { chat = MessageManager.getInstance().getChat(account, UserJid.from(room)); } catch (UserJid.UserJidCreateException e) { return null; } if (chat != null && chat instanceof RoomChat) { return (RoomChat) chat; } return null; } public boolean hasRoom(AccountJid account, UserJid room) { EntityBareJid entityBareJid = room.getJid().asEntityBareJidIfPossible(); if (entityBareJid == null) { return false; } return hasRoom(account, room.getJid().asEntityBareJidIfPossible()); } /** * @return Whether there is such room. */ public boolean hasRoom(AccountJid account, EntityBareJid room) { return getRoomChat(account, room) != null; } public boolean isMucPrivateChat(AccountJid account, UserJid user) { EntityBareJid entityBareJid = user.getJid().asEntityBareJidIfPossible(); if (entityBareJid == null) { return false; } return hasRoom(account, entityBareJid) && user.getJid().getResourceOrNull() != null; } public Resourcepart getNickname(AccountJid account, EntityBareJid room) { RoomChat roomChat = getRoomChat(account, room); if (roomChat == null) { return Resourcepart.EMPTY; } return roomChat.getNickname(); } /** * @param account * @param room * @return password or empty string if room does not exists. */ public String getPassword(AccountJid account, EntityBareJid room) { RoomChat roomChat = getRoomChat(account, room); if (roomChat == null) { return ""; } return roomChat.getPassword(); } /** * @return list of occupants or empty list. */ public Collection<Occupant> getOccupants(AccountJid account, EntityBareJid room) { RoomChat roomChat = getRoomChat(account, room); if (roomChat == null) { return Collections.emptyList(); } return roomChat.getOccupants(); } /** * @return <code>null</code> if there is no such invite. */ public RoomInvite getInvite(AccountJid account, EntityBareJid room) { try { return inviteProvider.get(account, UserJid.from(room)); } catch (UserJid.UserJidCreateException e) { return null; } } public void removeInvite(RoomInvite abstractRequest) { inviteProvider.remove(abstractRequest); } public void removeRoom(final AccountJid account, final EntityBareJid room) { removeInvite(getInvite(account, room)); RoomChat roomChat = getRoomChat(account, room); if (roomChat == null) { return; } leaveRoom(account, room); MessageManager.getInstance().removeChat(roomChat); try { RosterManager.onContactChanged(account, UserJid.from(room)); } catch (UserJid.UserJidCreateException e) { LogManager.exception(this, e); } Application.getInstance().runInBackgroundUserRequest(new Runnable() { @Override public void run() { RoomTable.getInstance().remove(account.toString(), room.toString()); } }); } /** * Creates or updates existed room. * */ public void createRoom(AccountJid account, EntityBareJid room, Resourcepart nickname, String password, boolean join) { removeInvite(getInvite(account, room)); AbstractChat chat = null; try { chat = MessageManager.getInstance().getChat(account, UserJid.from(room)); } catch (UserJid.UserJidCreateException e) { LogManager.exception(this, e); } RoomChat roomChat; if (chat == null || !(chat instanceof RoomChat)) { if (chat != null) { MessageManager.getInstance().removeChat(chat); } try { roomChat = RoomChat.create(account, room, nickname, password); MessageManager.getInstance().addChat(roomChat); } catch (UserJid.UserJidCreateException e) { LogManager.exception(this, e); } } else { roomChat = (RoomChat) chat; roomChat.setNickname(nickname); roomChat.setPassword(password); } requestToWriteRoom(account, room, nickname, password, join); if (join) { joinRoom(account, room, true); } } private void requestToWriteRoom(final AccountJid account, final EntityBareJid room, final Resourcepart nickname, final String password, final boolean join) { Application.getInstance().runInBackgroundUserRequest(new Runnable() { @Override public void run() { RoomTable.getInstance().write(account.toString(), room.toString(), nickname.toString(), password, join); } }); } /** * @return Whether room is disabled. */ public boolean isDisabled(final AccountJid account, final EntityBareJid room) { RoomChat roomChat = getRoomChat(account, room); return roomChat == null || roomChat.getState() == RoomState.unavailable; } /** * @return Whether connected is establish or connection is in progress. */ public boolean inUse(final AccountJid account, final EntityBareJid room) { RoomChat roomChat = getRoomChat(account, room); return roomChat != null && roomChat.getState().inUse(); } /** * Requests to join to the room. * * @param requested Whether user request to join the room. */ public void joinRoom(final AccountJid account, final EntityBareJid room, boolean requested) { final RoomChat roomChat; final Resourcepart nickname; final String password; roomChat = getRoomChat(account, room); if (roomChat == null) { Application.getInstance().onError(R.string.ENTRY_IS_NOT_FOUND); return; } RoomState state = roomChat.getState(); if (state == RoomState.available || state == RoomState.occupation) { Application.getInstance().onError(R.string.ALREADY_JOINED); return; } if (state == RoomState.creating || state == RoomState.joining) { Application.getInstance().onError(R.string.ALREADY_IN_PROGRESS); return; } nickname = roomChat.getNickname(); password = roomChat.getPassword(); requestToWriteRoom(account, room, nickname, password, true); AccountItem accountItem = AccountManager.getInstance().getAccount(account); if (accountItem == null) { return; } final MultiUserChat multiUserChat; try { multiUserChat = MultiUserChatManager.getInstanceFor(accountItem.getConnection()).getMultiUserChat(room); } catch (IllegalStateException e) { Application.getInstance().onError(R.string.NOT_CONNECTED); return; } roomChat.setState(RoomState.joining); roomChat.setMultiUserChat(multiUserChat); roomChat.setRequested(requested); Application.getInstance().runInBackgroundUserRequest(new Runnable() { @Override public void run() { try { if (roomChat.getMultiUserChat() != multiUserChat) { return; } multiUserChat.join(nickname, password); Application.getInstance().runOnUiThread(new Runnable() { @Override public void run() { if (roomChat.getMultiUserChat() != multiUserChat) { return; } if (roomChat.getState() == RoomState.joining) { roomChat.setState(RoomState.occupation); } removeAuthorizationError(account, room); try { RosterManager.onContactChanged(account, UserJid.from(room)); } catch (UserJid.UserJidCreateException e) { LogManager.exception(this, e); } } }); return; } catch (final XMPPException.XMPPErrorException e) { Application.getInstance().runOnUiThread(new Runnable() { @Override public void run() { if (roomChat.getMultiUserChat() != multiUserChat) { return; } roomChat.setState(RoomState.error); addAuthorizationError(account, room); XMPPError xmppError = e.getXMPPError(); if (xmppError != null && xmppError.getCondition() == XMPPError.Condition.conflict) { Application.getInstance().onError(R.string.NICK_ALREADY_USED); } else if (xmppError != null && xmppError.getCondition() == XMPPError.Condition.not_authorized) { Application.getInstance().onError(R.string.AUTHENTICATION_FAILED); } else { Application.getInstance().onError(R.string.NOT_CONNECTED); } try { RosterManager.onContactChanged(account, UserJid.from(room)); } catch (UserJid.UserJidCreateException e) { LogManager.exception(this, e); } } }); return; } catch (Exception e) { LogManager.exception(this, e); } Application.getInstance().runOnUiThread(new Runnable() { @Override public void run() { if (roomChat.getMultiUserChat() != multiUserChat) { return; } roomChat.setState(RoomState.waiting); Application.getInstance().onError(R.string.NOT_CONNECTED); try { RosterManager.onContactChanged(account, UserJid.from(room)); } catch (UserJid.UserJidCreateException e) { LogManager.exception(this, e); } } }); } }); } public void leaveRoom(AccountJid account, EntityBareJid room) { final MultiUserChat multiUserChat; RoomChat roomChat = getRoomChat(account, room); if (roomChat == null) { return; } multiUserChat = roomChat.getMultiUserChat(); roomChat.setState(RoomState.unavailable); roomChat.setRequested(false); roomChat.newAction(roomChat.getNickname(), null, ChatAction.leave); requestToWriteRoom(account, room, roomChat.getNickname(), roomChat.getPassword(), false); if (multiUserChat != null) { Application.getInstance().runInBackgroundUserRequest(new Runnable() { @Override public void run() { try { multiUserChat.leave(); } catch (SmackException.NotConnectedException | InterruptedException e) { LogManager.exception(this, e); } } }); } try { RosterManager.onContactChanged(account, UserJid.from(room)); } catch (UserJid.UserJidCreateException e) { LogManager.exception(this, e); } } @Override public void onStanza(ConnectionItem connection, Stanza stanza) { if (!(connection instanceof AccountItem)) { return; } AccountJid account = ((AccountItem) connection).getAccount(); Jid from = stanza.getFrom(); if (from == null || !(stanza instanceof Message)) { return; } Message message = (Message) stanza; if (message.getType() != Message.Type.normal && message.getType() != Message.Type.chat) { return; } MUCUser mucUser = MUCUser.from(stanza); if (mucUser == null || mucUser.getInvite() == null) { return; } RoomChat roomChat = getRoomChat(account, from.asEntityBareJidIfPossible()); if (roomChat == null || !roomChat.getState().inUse()) { UserJid inviter = null; try { inviter = UserJid.from(mucUser.getInvite().getFrom()); } catch (UserJid.UserJidCreateException e) { LogManager.exception(this, e); } if (inviter == null) { try { inviter = UserJid.from(from); } catch (UserJid.UserJidCreateException e) { LogManager.exception(this, e); } } try { inviteProvider.add(new RoomInvite(account, UserJid.from(from), inviter, mucUser.getInvite().getReason(), mucUser.getPassword()), true); } catch (UserJid.UserJidCreateException e) { LogManager.exception(this, e); } } } /** * Sends invitation. * * @throws NetworkException */ public void invite(AccountJid account, EntityBareJid room, UserJid user) throws NetworkException { RoomChat roomChat = getRoomChat(account, room); if (roomChat == null || roomChat.getState() != RoomState.available) { Application.getInstance().onError(R.string.NOT_CONNECTED); return; } Message message = new Message(room); MUCUser mucUser = new MUCUser(); MUCUser.Invite invite = new MUCUser.Invite("", null, user.getBareJid().asEntityBareJidIfPossible()); mucUser.setInvite(invite); message.addExtension(mucUser); StanzaSender.sendStanza(account, message); roomChat.putInvite(message.getStanzaId(), user); roomChat.newAction(roomChat.getNickname(), user.toString(), ChatAction.invite_sent); } public void removeAuthorizationError(AccountJid account, EntityBareJid room) { try { authorizationErrorProvider.remove(account, UserJid.from(room)); } catch (UserJid.UserJidCreateException e) { LogManager.exception(this, e); } } public void addAuthorizationError(AccountJid account, EntityBareJid room) { try { authorizationErrorProvider.add(new RoomAuthorizationError(account, UserJid.from(room)), null); } catch (UserJid.UserJidCreateException e) { LogManager.exception(this, e); } } public interface HostedRoomsListener { void onHostedRoomsReceived(Collection<HostedRoom> hostedRooms); } public static void requestHostedRooms(final AccountJid account, final DomainBareJid serviceName, final HostedRoomsListener listener) { AccountItem accountItem = AccountManager.getInstance().getAccount(account); if (accountItem == null) { listener.onHostedRoomsReceived(null); return; } final XMPPConnection xmppConnection = accountItem.getConnection(); if (!xmppConnection.isAuthenticated()) { listener.onHostedRoomsReceived(null); return; } Application.getInstance().runInBackgroundUserRequest(new Runnable() { @Override public void run() { Collection<HostedRoom> hostedRooms = null; try { hostedRooms = MultiUserChatManager.getInstanceFor(xmppConnection).getHostedRooms(serviceName); } catch (SmackException.NoResponseException | XMPPException.XMPPErrorException | SmackException.NotConnectedException | InterruptedException | MultiUserChatException.NotAMucServiceException e) { LogManager.exception(this, e); } final Collection<HostedRoom> finalHostedRooms = hostedRooms; Application.getInstance().runOnUiThread(new Runnable() { @Override public void run() { listener.onHostedRoomsReceived(finalHostedRooms); } }); } }); } public interface RoomInfoListener { void onRoomInfoReceived(RoomInfo finalRoomInfo); } public static void requestRoomInfo(final AccountJid account, final EntityBareJid roomJid, final RoomInfoListener listener) { AccountItem accountItem = AccountManager.getInstance().getAccount(account); if (accountItem == null) { listener.onRoomInfoReceived(null); return; } final XMPPConnection xmppConnection = accountItem.getConnection(); if (!xmppConnection.isAuthenticated()) { listener.onRoomInfoReceived(null); return; } Application.getInstance().runInBackgroundUserRequest(new Runnable() { @Override public void run() { RoomInfo roomInfo = null; try { LogManager.i(MUCManager.class, "Requesting room info " + roomJid); roomInfo = MultiUserChatManager.getInstanceFor(xmppConnection).getRoomInfo(roomJid); } catch (SmackException.NoResponseException | XMPPException.XMPPErrorException | SmackException.NotConnectedException | InterruptedException e) { LogManager.exception(this, e); } final RoomInfo finalRoomInfo = roomInfo; Application.getInstance().runOnUiThread(new Runnable() { @Override public void run() { listener.onRoomInfoReceived(finalRoomInfo); } }); } }); } }