@Override protected void onStart() { super.onStart(); if (mKey == null) { PersonalKeyRunnable action = new PersonalKeyRunnable() { public void run(PersonalKey key) { mKey = key; if (mValidator != null) // this will release the waiting lock mValidator.setKey(mKey); } }; // random passphrase (40 characters!!!!) mPassphrase = StringUtils.randomString(40); mKeyReceiver = new KeyGeneratedReceiver(mHandler, action); IntentFilter filter = new IntentFilter(KeyPairGeneratorService.ACTION_GENERATE); lbm.registerReceiver(mKeyReceiver, filter); Toast.makeText(this, R.string.msg_generating_keypair, Toast.LENGTH_LONG).show(); Intent i = new Intent(this, KeyPairGeneratorService.class); i.setAction(KeyPairGeneratorService.ACTION_GENERATE); startService(i); } }
public void sendMessage(String text) { final Message message = new Message(); if (threadID == null) { threadID = StringUtils.randomString(6); } message.setThread(threadID); // Set the body of the message using typedMessage message.setBody(text); // IF there is no body, just return and do nothing if (!ModelUtil.hasLength(text)) { return; } // Fire Message Filters SparkManager.getChatManager().filterOutgoingMessage(this, message); // Fire Global Filters SparkManager.getChatManager().fireGlobalMessageSentListeners(this, message); sendMessage(message); sendNotification = true; }
/** @author Kir */ public class JabberTransport implements Transport, ConnectionListener, Disposable { @NonNls private static final Logger LOG = Logger.getLogger(JabberTransport.class); private static final int RESPONSE_TIMEOUT = 120 * 1000; @NonNls public static final String CODE = "Jabber"; private final JabberUI myUI; private final JabberFacade myFacade; private final UserModel myUserModel; private final IDEtalkListener myUserModelListener = new MyUserModelListener(); private final AsyncMessageDispatcher myDispatcher; private RosterListener myRosterListener; private PacketListener mySubscribeListener; private PacketListener myMessageListener; private final JabberUserFinder myUserFinder; private final IDEFacade myIdeFacade; private final String myThreadIdPrefix = StringUtils.randomString(5); private int myCurrentThreadId; private boolean myIgnoreUserEvents; private PresenceMode myPresenceMode; private final Map<User, UserPresence> myUser2Presence = new HashMap<>(); private final Set<String> myIDEtalkUsers = new HashSet<>(); private final Map<String, String> myUser2Thread = Collections.synchronizedMap(new HashMap<String, String>()); @NonNls private static final String RESPONSE = "response"; private final IgnoreList myIgnoreList; // negative value disables reconnect private int myReconnectTimeout = Integer.parseInt(System.getProperty("ideTalk.reconnect", "30")) * 1000; private Future<?> myReconnectProcess; public JabberTransport( JabberUI UI, JabberFacade facade, UserModel userModel, AsyncMessageDispatcher messageDispatcher, JabberUserFinder userFinder) { Roster.setDefaultSubscriptionMode(Roster.SubscriptionMode.manual); // XMPPConnection.DEBUG_ENABLED = true; JDOMExtension.init(); myUI = UI; myFacade = facade; myUserModel = userModel; myDispatcher = messageDispatcher; myUserFinder = userFinder; myIdeFacade = messageDispatcher.getIdeFacade(); myIgnoreList = new IgnoreList(myIdeFacade); myFacade.addConnectionListener(this); getBroadcaster().addListener(myUserModelListener); } private EventBroadcaster getBroadcaster() { return myUserModel.getBroadcaster(); } @Override public String getName() { return CODE; } @Override public void initializeProject(String projectName, MutablePicoContainer projectLevelContainer) { myUI.initPerProject(projectLevelContainer); myIdeFacade.runOnPooledThread(() -> myFacade.connect()); } @Override public User[] findUsers(ProgressIndicator progressIndicator) { if (isOnline()) { return myUserFinder.findUsers(progressIndicator); } return new User[0]; } @Override public Class<? extends NamedUserCommand> getSpecificFinderClass() { return FindByJabberIdCommand.class; } @Override public boolean isOnline() { return myFacade.isConnectedAndAuthenticated(); } @Override public UserPresence getUserPresence(User user) { UserPresence presence = myUser2Presence.get(user); if (presence == null) { presence = new UserPresence(false); myUser2Presence.put(user, presence); } return presence; } private UserPresence _getUserPresence(User user) { Presence presence = _getPresence(user); if (presence != null && presence.getType() == Presence.Type.available) { Presence.Mode mode = presence.getMode(); final PresenceMode presenceMode; //noinspection IfStatementWithTooManyBranches if (mode == Presence.Mode.away) { presenceMode = PresenceMode.AWAY; } else if (mode == Presence.Mode.dnd) { presenceMode = PresenceMode.DND; } else if (mode == Presence.Mode.xa) { presenceMode = PresenceMode.EXTENDED_AWAY; } else { presenceMode = PresenceMode.AVAILABLE; } return new UserPresence(presenceMode); } return new UserPresence(false); } @Override @NonNls public Icon getIcon(UserPresence userPresence) { return UIUtil.getIcon( userPresence, IdetalkCoreIcons.IdeTalk.Jabber, IdetalkCoreIcons.IdeTalk.Jabber_dnd); } @Override public boolean isSelf(User user) { return myFacade.isConnectedAndAuthenticated() && getSimpleId(myFacade.getConnection().getUser()).equals(user.getName()); } @Override public String[] getProjects(User user) { return ArrayUtil.EMPTY_STRING_ARRAY; } @Override @Nullable public String getAddressString(User user) { return null; } @Override public synchronized void sendXmlMessage(User user, final XmlMessage xmlMessage) { if (!myUI.connectAndLogin(null)) { return; } final String threadId = getThreadId(user); final PacketCollector packetCollector = myFacade.getConnection().createPacketCollector(new ThreadFilter(threadId)); doSendMessage(xmlMessage, user, threadId); if (xmlMessage.needsResponse()) { //noinspection HardCodedStringLiteral final Runnable responseWaiterRunnable = () -> { try { processResponse(xmlMessage, packetCollector); } finally { packetCollector.cancel(); } }; myIdeFacade.runOnPooledThread(responseWaiterRunnable); } else { packetCollector.cancel(); } } String getThreadId(User user) { String id = myUser2Thread.get(user.getName()); if (id == null) { id = myThreadIdPrefix + myCurrentThreadId++; myUser2Thread.put(user.getName(), id); } return id; } @Override public void setOwnPresence(UserPresence userPresence) { if (isOnline() && !userPresence.isOnline()) { myFacade.disconnect(); } else if (!isOnline() && userPresence.isOnline()) { myUI.connectAndLogin(null); } if (isOnline() && presenceModeChanged(userPresence.getPresenceMode())) { myFacade.setOnlinePresence(userPresence); myPresenceMode = userPresence.getPresenceMode(); } } @Override public boolean hasIdeTalkClient(User user) { return myIDEtalkUsers.contains(user.getName()); } private boolean presenceModeChanged(PresenceMode presenceMode) { return myPresenceMode == null || myPresenceMode != presenceMode; } private static void processResponse(XmlMessage xmlMessage, PacketCollector collector) { boolean gotResponse = false; while (!gotResponse) { Message response = (Message) collector.nextResult(RESPONSE_TIMEOUT); if (response == null) break; final Collection<PacketExtension> extensions = response.getExtensions(); for (PacketExtension o : extensions) { if (o instanceof JDOMExtension) { JDOMExtension extension = (JDOMExtension) o; if (RESPONSE.equals(extension.getElement().getName())) { xmlMessage.processResponse(extension.getElement()); gotResponse = true; break; } } } } } private Message doSendMessage(XmlMessage xmlMessage, User user, String threadId) { Element element = new Element(xmlMessage.getTagName(), xmlMessage.getTagNamespace()); xmlMessage.fillRequest(element); Message message = createBaseMessage(user, element.getText()); message.setThread(threadId); message.addExtension(new JDOMExtension(element)); myFacade.getConnection().sendPacket(message); return message; } static Message createBaseMessage(User user, String message) { Message msg = new Message(user.getName(), Message.Type.CHAT); msg.setBody(message); return msg; } @Override public void connected(XMPPConnection connection) { LOG.info("Jabber connected"); if (mySubscribeListener == null) { mySubscribeListener = new MySubscribeListener(); connection.addPacketListener(mySubscribeListener, new PacketTypeFilter(Presence.class)); } if (myMessageListener == null) { myMessageListener = new MyMessageListener(); connection.addPacketListener(myMessageListener, new PacketTypeFilter(Message.class)); } } @Override public void authenticated() { LOG.info("Jabber authenticated: " + myFacade.getConnection().getUser()); if (myRosterListener == null) { myRosterListener = new MyRosterListener(); getRoster().addRosterListener(myRosterListener); } myUserFinder.registerForProject(myFacade.getMyAccount().getJabberId()); if (!hasJabberContacts()) { synchronizeRoster(false); } } private boolean hasJabberContacts() { User[] users = myUserModel.getAllUsers(); for (User user : users) { if (user.getTransportCode().equals(getName())) return true; } return false; } @Override public void disconnected(boolean onError) { final XMPPConnection connection = myFacade.getConnection(); LOG.info("Jabber disconnected: " + connection.getUser()); connection.removePacketListener(mySubscribeListener); mySubscribeListener = null; connection.removePacketListener(myMessageListener); myMessageListener = null; final Roster roster = connection.getRoster(); if (roster != null) { roster.removeRosterListener(myRosterListener); } myRosterListener = null; myIDEtalkUsers.clear(); myUser2Presence.clear(); myUser2Thread.clear(); if (onError && reconnectEnabledAndNotStarted()) { LOG.warn(getMsg("jabber.server.was.disconnected", myReconnectTimeout / 1000)); myReconnectProcess = myIdeFacade.runOnPooledThread(new MyReconnectRunnable()); } } private boolean reconnectEnabledAndNotStarted() { return (myReconnectProcess == null || myReconnectProcess.isDone()) && myReconnectTimeout >= 0; } @Override public void dispose() { getBroadcaster().removeListener(myUserModelListener); myFacade.removeConnectionListener(this); } private void updateUserPresence(String jabberId) { LOG.debug("Presence changed for " + jabberId); final User user = myUserModel.findUser(getSimpleId(jabberId), getName()); if (user != null) { updateIsIDEtalkClient(jabberId, user); final UserPresence presence = _getUserPresence(user); IDEtalkEvent event = createPresenceChangeEvent(user, presence); if (event != null) { getBroadcaster().doChange(event, () -> myUser2Presence.put(user, presence)); } } } private void updateIsIDEtalkClient(String jabberId, User user) { if (getResource(jabberId) .toLowerCase() .startsWith(JabberFacade.IDETALK_RESOURCE.toLowerCase())) { myIDEtalkUsers.add(user.getName()); } else { myIDEtalkUsers.remove(user.getName()); } } @Nullable private IDEtalkEvent createPresenceChangeEvent(User user, UserPresence newPresence) { UserPresence oldPresence = getUserPresence(user); if (!newPresence.equals(oldPresence)) { if (newPresence.isOnline() ^ oldPresence.isOnline()) { return newPresence.isOnline() ? new UserEvent.Online(user) : new UserEvent.Offline(user); } else { return new UserEvent.Updated( user, PRESENCE, oldPresence.getPresenceMode(), newPresence.getPresenceMode()); } } return null; } private void updateJabberUsers(boolean removeUsersNotInRoster) { LOG.debug("Roster changed - update user model"); Set<User> currentUsers = new HashSet<>(Arrays.asList(myUserModel.getAllUsers())); for (RosterEntry rosterEntry : getRoster().getEntries()) { User user = addJabberUserToUserModelOrUpdateInfo(rosterEntry); currentUsers.remove(user); } if (removeUsersNotInRoster) { removeUsers(currentUsers); } if (LOG.isDebugEnabled()) { LOG.debug("Roster synchronized: " + Arrays.asList(myUserModel.getAllUsers())); } } private void removeUsers(Set<User> currentUsers) { for (User user : currentUsers) { myUserModel.removeUser(user); } } private User addJabberUserToUserModelOrUpdateInfo(RosterEntry rosterEntry) { // System.out.println("rosterEntry.getName() = " + rosterEntry.getName()); // System.out.println("rosterEntry.getUser() = " + rosterEntry.getUser()); User user = myUserModel.createUser(getSimpleId(rosterEntry.getUser()), getName()); String newGroup = getUserGroup(rosterEntry); if (newGroup != null) { user.setGroup(newGroup, myUserModel); } user.setDisplayName(rosterEntry.getName(), myUserModel); myUserModel.addUser(user); String jabberId = getCurrentJabberID(user, rosterEntry); updateIsIDEtalkClient(jabberId, user); return user; } private String getCurrentJabberID(User user, RosterEntry rosterEntry) { Presence presence = _getPresence(user); String jabberId = null; if (presence != null) { jabberId = presence.getFrom(); } if (jabberId == null) jabberId = rosterEntry.getUser(); if (jabberId == null) jabberId = rosterEntry.getName(); return jabberId; } static String getResource(String userName) { int lastSlash = userName.indexOf('/'); if (lastSlash != -1) { return userName.substring(lastSlash + 1); } return ""; } static String getSimpleId(String userName) { String id = userName; int lastSlash = id.indexOf('/'); if (lastSlash != -1) { id = id.substring(0, lastSlash); } return id; } @Nullable private static String getUserGroup(RosterEntry rosterEntry) { String group = null; for (RosterGroup rosterGroup : rosterEntry.getGroups()) { group = rosterGroup.getName(); } return group; } private Roster getRoster() { final Roster roster = myFacade.getConnection().getRoster(); assert roster != null; return roster; } public JabberFacade getFacade() { return myFacade; } boolean isUserInMyContactListAndActive(String userName) { User user = myUserModel.findUser(getSimpleId(userName), getName()); return user != null && user.isOnline(); } @Nullable private Presence _getPresence(User user) { if (!isOnline()) return null; return getRoster().getPresence(user.getName()); } private User self() { return myUserModel.createUser(myFacade.getMyAccount().getJabberId(), getName()); } public static JabberTransport getInstance() { return (JabberTransport) Pico.getInstance().getComponentInstanceOfType(JabberTransport.class); } public void synchronizeRoster(boolean removeUsersNotInRoster) { updateJabberUsers(removeUsersNotInRoster); } public void runIngnoringUserEvents(Runnable runnable) { try { myIgnoreUserEvents = true; runnable.run(); } finally { myIgnoreUserEvents = false; } } /** -1 disables reconnect */ public void setReconnectTimeout(int milliseconds) { myReconnectTimeout = milliseconds; } private class MyRosterListener implements RosterListener { @Override public void entriesAdded(Collection addresses) { updateJabberUsers(false); } @Override public void entriesUpdated(Collection addresses) { updateJabberUsers(false); } @Override public void entriesDeleted(Collection addresses) { updateJabberUsers(false); } @Override public void presenceChanged(final String string) { updateUserPresence(string); } } @SuppressWarnings({"RefusedBequest"}) private class MyUserModelListener extends TransportUserListener { MyUserModelListener() { super(JabberTransport.this); } @Override protected void processBeforeChange(UserEvent event) { super.processBeforeChange(event); event.accept( new EventVisitor() { @Override public void visitUserAdded(UserEvent.Added event) { event.getUser().setCanAccessMyFiles(false, myUserModel); } }); } @Override protected void processAfterChange(UserEvent event) { if (myIgnoreUserEvents) return; event.accept( new EventVisitor() { @Override public void visitUserRemoved(UserEvent.Removed event) { synchronizeWithJabberIfPossible(event); } @Override public void visitUserUpdated(UserEvent.Updated event) { if (GROUP.equals(event.getPropertyName()) || DISPLAY_NAME.equals(event.getPropertyName())) { synchronizeWithJabberIfPossible(event); } } }); } private void synchronizeWithJabberIfPossible(UserEvent event) { if (event.getUser().getTransportCode().equals(getName()) && myFacade.isConnectedAndAuthenticated()) { myDispatcher.sendNow(self(), new JabberSyncUserMessage(event)); } } } private class MySubscribeListener implements PacketListener { @Override public void processPacket(Packet packet) { final Presence presence = ((Presence) packet); if (presence.getType() != Presence.Type.subscribe) return; LOG.info("Subscribe request from " + presence.getFrom()); if (myIgnoreList.isIgnored(presence.getFrom())) { LOG.info(presence.getFrom() + " in ignore list"); return; } if (isUserInMyContactListAndActive(presence.getFrom()) || Pico.isUnitTest()) { acceptSubscription(presence, true); return; } UIUtil.invokeLater( () -> acceptSubscription(presence, myUI.shouldAcceptSubscriptionRequest(presence))); } private void acceptSubscription(final Presence presence, boolean subscribe) { if (!isOnline()) return; myFacade.changeSubscription(presence.getFrom(), subscribe); if (subscribe) { String from = getSimpleId(presence.getFrom()); LOG.info("Add " + from + " to the roster"); try { getRoster().createEntry(from, from, new String[] {UserModel.DEFAULT_GROUP}); } catch (XMPPException e) { LOG.warn(e); } } } } private class MyMessageListener implements PacketListener { @Override public void processPacket(Packet packet) { try { doProcessPacket(packet); } catch (Throwable e) { LOG.error(e.getMessage(), e); } } private void doProcessPacket(Packet packet) { final Message message = ((Message) packet); if (message.getType() == Message.Type.ERROR) { UIUtil.invokeLater( () -> { String from = (message.getFrom() != null) ? getMsg("from.0.lf", message.getFrom()) : ""; LOG.warn( getMsg( "jabber.error.text", from, (message.getError() == null ? "N/A" : message.getError().toString()))); }); return; } if (myIgnoreList.isIgnored(packet.getFrom())) { return; } Element element = null; for (PacketExtension o : message.getExtensions()) { if (o instanceof JDOMExtension) { element = ((JDOMExtension) o).getElement(); } } if (element != null && !RESPONSE.equals(element.getName())) { processAndSendResponse(element, message); } else if (element == null && message.getBody() != null) { // Some simple Jabber Message MessageEvent event = EventFactory.createMessageEvent( JabberTransport.this, getFrom(message), message.getBody()); if (message.getThread() != null) { myUser2Thread.put(getFrom(message), message.getThread()); } getBroadcaster().fireEvent(event); } } private void processAndSendResponse(Element element, Message message) { Element response = new Element(RESPONSE, Transport.NAMESPACE); XmlResponseProvider provider = XmlResponseProvider.getProvider(element, getBroadcaster()); if (provider.processAndFillResponse( response, element, JabberTransport.this, getFrom(message))) { Message responseMessage = new Message(getFrom(message)); responseMessage.addExtension(new JDOMExtension(response)); responseMessage.setThread(message.getThread()); myFacade.getConnection().sendPacket(responseMessage); } } private String getFrom(Message message) { return getSimpleId(message.getFrom()); } } private class MyReconnectRunnable implements Runnable { @Override public void run() { try { Thread.sleep(myReconnectTimeout); if (myFacade.connect() != null && myFacade.getMyAccount().isLoginAllowed()) { myReconnectProcess = myIdeFacade.runOnPooledThread(this); } } catch (InterruptedException ignored) { // return } } } }
public class ChatSessionAdapter extends org.awesomeapp.messenger.service.IChatSession.Stub { private static final String NON_CHAT_MESSAGE_SELECTION = Imps.Messages.TYPE + "!=" + Imps.MessageType.INCOMING + " AND " + Imps.Messages.TYPE + "!=" + Imps.MessageType.OUTGOING; /** The registered remote listeners. */ final RemoteCallbackList<IChatListener> mRemoteListeners = new RemoteCallbackList<IChatListener>(); ImConnectionAdapter mConnection; ChatSessionManagerAdapter mChatSessionManager; ChatSession mChatSession; ListenerAdapter mListenerAdapter; boolean mIsGroupChat; StatusBarNotifier mStatusBarNotifier; private ContentResolver mContentResolver; /*package*/ Uri mChatURI; private Uri mMessageURI; private boolean mConvertingToGroupChat; private static final int MAX_HISTORY_COPY_COUNT = 10; private HashMap<String, Integer> mContactStatusMap = new HashMap<String, Integer>(); private boolean mHasUnreadMessages; private RemoteImService service = null; private HashMap<String, OtrChatSessionAdapter> mOtrChatSessions; private SessionStatus mLastSessionStatus = null; private OtrDataHandler mDataHandler; private IDataListener mDataListener; private DataHandlerListenerImpl mDataHandlerListener; private boolean mAcceptTransfer = false; private boolean mWaitingForResponse = false; private boolean mAcceptAllTransfer = true; // TODO set this via preference, but default true private String mLastFileUrl = null; private long mContactId; public ChatSessionAdapter( ChatSession chatSession, ImConnectionAdapter connection, boolean isNewSession) { mChatSession = chatSession; mConnection = connection; service = connection.getContext(); mContentResolver = service.getContentResolver(); mStatusBarNotifier = service.getStatusBarNotifier(); mChatSessionManager = (ChatSessionManagerAdapter) connection.getChatSessionManager(); mListenerAdapter = new ListenerAdapter(); mOtrChatSessions = new HashMap<String, OtrChatSessionAdapter>(); ImEntity participant = mChatSession.getParticipant(); if (participant instanceof ChatGroup) { init((ChatGroup) participant, isNewSession); } else { init((Contact) participant, isNewSession); } initOtrChatSession(participant); } private void initOtrChatSession(ImEntity participant) { try { if (mConnection != null) { mDataHandler = new OtrDataHandler(mChatSession); mDataHandlerListener = new DataHandlerListenerImpl(); mDataHandler.setDataListener(mDataHandlerListener); OtrChatManager cm = service.getOtrChatManager(); cm.addOtrEngineListener(mListenerAdapter); mChatSession.setMessageListener(new OtrChatListener(cm, mListenerAdapter)); if (participant instanceof Contact) { String key = participant.getAddress().getAddress(); if (!mOtrChatSessions.containsKey(key)) { OtrChatSessionAdapter adapter = new OtrChatSessionAdapter( mConnection.getLoginUser().getAddress().getAddress(), participant, cm); mOtrChatSessions.put(key, adapter); } } else if (participant instanceof ChatGroup) { ChatGroup group = (ChatGroup) mChatSession.getParticipant(); for (Contact contact : group.getMembers()) { String key = contact.getAddress().getAddress(); if (!mOtrChatSessions.containsKey(key)) { OtrChatSessionAdapter adapter = new OtrChatSessionAdapter( mConnection.getLoginUser().getAddress().getAddress(), contact, cm); mOtrChatSessions.put(key, adapter); } } } mDataHandler.setChatId(getId()); } } catch (NullPointerException npe) { Log.e(ImApp.LOG_TAG, "error init OTR session", npe); } } public synchronized IOtrChatSession getDefaultOtrChatSession() { if (mOtrChatSessions.size() > 0) return mOtrChatSessions.entrySet().iterator().next().getValue(); else return null; } public void presenceChanged(int newPresence) { if (mChatSession.getParticipant() instanceof Contact) { ((Contact) mChatSession.getParticipant()).getPresence().setStatus(newPresence); try { if (newPresence == Presence.AVAILABLE) { if (hasPostponedMessages()) { if (getDefaultOtrChatSession().isChatEncrypted()) sendPostponedMessages(); else { // getDefaultOtrChatSession().stopChatEncryption(); getDefaultOtrChatSession().startChatEncryption(); } } } } catch (RemoteException re) { // nothing to see here } } } private void init(ChatGroup group, boolean isNewSession) { mIsGroupChat = true; mContactId = insertOrUpdateGroupContactInDb(group); group.addMemberListener(mListenerAdapter); try { mChatSessionManager .getChatGroupManager() .joinChatGroupAsync(group.getAddress(), group.getName()); mMessageURI = Imps.Messages.getContentUriByThreadId(mContactId); mChatURI = ContentUris.withAppendedId(Imps.Chats.CONTENT_URI, mContactId); if (isNewSession) insertOrUpdateChat(""); for (Contact c : group.getMembers()) { mContactStatusMap.put(c.getName(), c.getPresence().getStatus()); } } catch (Exception e) { // TODO Auto-generated catch block e.printStackTrace(); } } private void init(Contact contact, boolean isNewSession) { mIsGroupChat = false; ContactListManagerAdapter listManager = (ContactListManagerAdapter) mConnection.getContactListManager(); mContactId = listManager.queryOrInsertContact(contact); mChatURI = ContentUris.withAppendedId(Imps.Chats.CONTENT_URI, mContactId); if (isNewSession) insertOrUpdateChat(null); mMessageURI = Imps.Messages.getContentUriByThreadId(mContactId); mContactStatusMap.put(contact.getName(), contact.getPresence().getStatus()); } public void reInit() { // insertOrUpdateChat(null); } private ChatGroupManager getGroupManager() { return mConnection.getAdaptee().getChatGroupManager(); } public ChatSession getAdaptee() { return mChatSession; } public Uri getChatUri() { return mChatURI; } public String[] getParticipants() { if (mIsGroupChat) { Contact self = mConnection.getLoginUser(); ChatGroup group = (ChatGroup) mChatSession.getParticipant(); List<Contact> members = group.getMembers(); String[] result = new String[members.size() - 1]; int index = 0; for (Contact c : members) { if (!c.equals(self)) { result[index++] = c.getAddress().getAddress(); } } return result; } else { return new String[] {mChatSession.getParticipant().getAddress().getAddress()}; } } /** * Convert this chat session to a group chat. If it's already a group chat, nothing will happen. * The method works in async mode and the registered listener will be notified when it's converted * to group chat successfully. * * <p>Note that the method is not thread-safe since it's always called from the UI and Android * uses single thread mode for UI. */ public void convertToGroupChat(String nickname) { if (mIsGroupChat || mConvertingToGroupChat) { return; } mConvertingToGroupChat = true; new ChatConvertor().convertToGroupChat(nickname); } public boolean isGroupChatSession() { return mIsGroupChat; } public String getName() { if (isGroupChatSession()) return ((ChatGroup) mChatSession.getParticipant()).getName(); else return ((Contact) mChatSession.getParticipant()).getName(); } public String getAddress() { return mChatSession.getParticipant().getAddress().getAddress(); } public long getId() { return ContentUris.parseId(mChatURI); } public void inviteContact(String contact) { if (!mIsGroupChat) { return; } ContactListManagerAdapter listManager = (ContactListManagerAdapter) mConnection.getContactListManager(); Contact invitee = new Contact(new XmppAddress(contact), contact); getGroupManager().inviteUserAsync((ChatGroup) mChatSession.getParticipant(), invitee); } public void leave() { if (mIsGroupChat) { getGroupManager().leaveChatGroupAsync((ChatGroup) mChatSession.getParticipant()); } mContentResolver.delete(mMessageURI, null, null); mContentResolver.delete(mChatURI, null, null); mStatusBarNotifier.dismissChatNotification(mConnection.getProviderId(), getAddress()); mChatSessionManager.closeChatSession(this); } public void leaveIfInactive() { // if (mChatSession.getHistoryMessages().isEmpty()) { leave(); // } } public boolean sendKnock() { return mChatSession.sendKnock(mConnection.getLoginUser().getAddress().getAddress()); } public void sendMessage(String text, boolean isResend) { if (mConnection.getState() != ImConnection.LOGGED_IN) { // connection has been suspended, save the message without send it long now = System.currentTimeMillis(); insertMessageInDb(null, text, now, Imps.MessageType.POSTPONED); return; } org.awesomeapp.messenger.model.Message msg = new org.awesomeapp.messenger.model.Message(text); msg.setID(nextID()); msg.setFrom(mConnection.getLoginUser().getAddress()); msg.setType(Imps.MessageType.OUTGOING); long sendTime = System.currentTimeMillis(); if (!isResend) { insertMessageInDb(null, text, sendTime, msg.getType(), 0, msg.getID()); insertOrUpdateChat(text); } int newType = mChatSession.sendMessageAsync(msg); if (msg.getDateTime() != null) sendTime = msg.getDateTime().getTime(); updateMessageInDb(msg.getID(), newType, sendTime); } public boolean offerData(String offerId, String url, String type) { if (mConnection.getState() == ImConnection.SUSPENDED) { // TODO send later return false; } HashMap<String, String> headers = null; if (type != null) { headers = new HashMap<>(); headers.put("Mime-Type", type); } try { Address localUser = mConnection.getLoginUser().getAddress(); if (mChatSession.getParticipant() instanceof Contact) { Address remoteUser = new XmppAddress(getDefaultOtrChatSession().getRemoteUserId()); mDataHandler.offerData(offerId, localUser, remoteUser, url, headers); } else if (mChatSession.getParticipant() instanceof ChatGroup) { ChatGroup group = (ChatGroup) mChatSession.getParticipant(); for (Contact member : group.getMembers()) { mDataHandler.offerData(offerId, localUser, member.getAddress(), url, headers); } } return true; } catch (Exception ioe) { Log.w(ImApp.LOG_TAG, "unable to offer data", ioe); return false; } } /** Sends a message to other participant(s) in this session without adding it to the history. */ /* public void sendMessageWithoutHistory(String text) { Message msg = new Message(text); // TODO OTRCHAT use a lower level method mChatSession.sendMessageAsync(msg); }*/ boolean hasPostponedMessages() { String[] projection = new String[] { BaseColumns._ID, Imps.Messages.BODY, Imps.Messages.PACKET_ID, Imps.Messages.DATE, Imps.Messages.TYPE, Imps.Messages.IS_DELIVERED }; String selection = Imps.Messages.TYPE + "=?"; boolean result = false; Cursor c = mContentResolver.query( mMessageURI, projection, selection, new String[] {Integer.toString(Imps.MessageType.POSTPONED)}, null); if (c == null) { RemoteImService.debug("Query error while querying postponed messages"); return false; } else if (c.getCount() > 0) { result = true; } c.close(); return true; } synchronized void sendPostponedMessages() { String[] projection = new String[] { BaseColumns._ID, Imps.Messages.BODY, Imps.Messages.PACKET_ID, Imps.Messages.DATE, Imps.Messages.TYPE, Imps.Messages.IS_DELIVERED }; String selection = Imps.Messages.TYPE + "=?"; Cursor c = mContentResolver.query( mMessageURI, projection, selection, new String[] {Integer.toString(Imps.MessageType.POSTPONED)}, null); if (c == null) { RemoteImService.debug("Query error while querying postponed messages"); return; } ArrayList<String> messages = new ArrayList<String>(); while (c.moveToNext()) messages.add(c.getString(1)); c.close(); removeMessageInDb(Imps.MessageType.POSTPONED); for (String body : messages) sendMessage(body, false); } public void registerChatListener(IChatListener listener) { if (listener != null) { mRemoteListeners.register(listener); if (mDataHandlerListener != null) mDataHandlerListener.checkLastTransferRequest(); } } public void unregisterChatListener(IChatListener listener) { if (listener != null) { mRemoteListeners.unregister(listener); } } public void markAsRead() { if (mHasUnreadMessages) { /** * we want to keep the last message now ContentValues values = new ContentValues(1); * values.put(Imps.Chats.LAST_UNREAD_MESSAGE, (String) null); * mConnection.getContext().getContentResolver().update(mChatURI, values, null, null); */ String baseUsername = mChatSession.getParticipant().getAddress().getBareAddress(); mStatusBarNotifier.dismissChatNotification(mConnection.getProviderId(), baseUsername); mHasUnreadMessages = false; } } String getNickName(String username) { ImEntity participant = mChatSession.getParticipant(); if (mIsGroupChat) { ChatGroup group = (ChatGroup) participant; List<Contact> members = group.getMembers(); for (Contact c : members) { if (username.equals(c.getAddress().getAddress())) { return c.getAddress().getResource(); } } // not found, impossible String[] parts = username.split("/"); return parts[parts.length - 1]; } else { return ((Contact) participant).getName(); } } void onConvertToGroupChatSuccess(ChatGroup group) { Contact oldParticipant = (Contact) mChatSession.getParticipant(); String oldAddress = getAddress(); // mChatSession.setParticipant(group); mChatSessionManager.updateChatSession(oldAddress, this); Uri oldChatUri = mChatURI; Uri oldMessageUri = mMessageURI; init(group, false); // copyHistoryMessages(oldParticipant); mContentResolver.delete(oldMessageUri, NON_CHAT_MESSAGE_SELECTION, null); mContentResolver.delete(oldChatUri, null, null); mListenerAdapter.notifyChatSessionConverted(); mConvertingToGroupChat = false; } /** * private void copyHistoryMessages(Contact oldParticipant) { * List<org.awesomeapp.messenger.model.Message> historyMessages = * mChatSession.getHistoryMessages(); int total = historyMessages.size(); int start = total > * MAX_HISTORY_COPY_COUNT ? total - MAX_HISTORY_COPY_COUNT : 0; for (int i = start; i < total; * i++) { org.awesomeapp.messenger.model.Message msg = historyMessages.get(i); boolean incoming = * msg.getFrom().equals(oldParticipant.getAddress()); String contact = incoming ? * oldParticipant.getName() : null; long time = msg.getDateTime().getTime(); * insertMessageInDb(contact, msg.getBody(), time, incoming ? Imps.MessageType.INCOMING : * Imps.MessageType.OUTGOING); } } */ void insertOrUpdateChat(String message) { ContentValues values = new ContentValues(2); values.put(Imps.Chats.LAST_MESSAGE_DATE, System.currentTimeMillis()); values.put(Imps.Chats.LAST_UNREAD_MESSAGE, message); values.put(Imps.Chats.GROUP_CHAT, mIsGroupChat); // ImProvider.insert() will replace the chat if it already exist. mContentResolver.insert(mChatURI, values); } private long insertOrUpdateGroupContactInDb(ChatGroup group) { // Insert a record in contacts table ContentValues values = new ContentValues(4); values.put(Imps.Contacts.USERNAME, group.getAddress().getAddress()); values.put(Imps.Contacts.NICKNAME, group.getName()); values.put(Imps.Contacts.CONTACTLIST, ContactListManagerAdapter.LOCAL_GROUP_LIST_ID); values.put(Imps.Contacts.TYPE, Imps.Contacts.TYPE_GROUP); Uri contactUri = ContentUris.withAppendedId( ContentUris.withAppendedId(Imps.Contacts.CONTENT_URI, mConnection.mProviderId), mConnection.mAccountId); ContactListManagerAdapter listManager = (ContactListManagerAdapter) mConnection.getContactListManager(); long id = listManager.queryGroup(group); if (id == -1) { id = ContentUris.parseId(mContentResolver.insert(contactUri, values)); ArrayList<ContentValues> memberValues = new ArrayList<ContentValues>(); Contact self = mConnection.getLoginUser(); for (Contact member : group.getMembers()) { if (!member.equals(self)) { // avoid to insert the user himself ContentValues memberValue = new ContentValues(2); memberValue.put(Imps.GroupMembers.USERNAME, member.getAddress().getAddress()); memberValue.put(Imps.GroupMembers.NICKNAME, member.getName()); memberValues.add(memberValue); } } if (!memberValues.isEmpty()) { ContentValues[] result = new ContentValues[memberValues.size()]; memberValues.toArray(result); Uri memberUri = ContentUris.withAppendedId(Imps.GroupMembers.CONTENT_URI, id); mContentResolver.bulkInsert(memberUri, result); } } return id; } void insertGroupMemberInDb(Contact member) { if (mChatURI != null) { ContentValues values1 = new ContentValues(2); values1.put(Imps.GroupMembers.USERNAME, member.getAddress().getAddress()); values1.put(Imps.GroupMembers.NICKNAME, member.getName()); ContentValues values = values1; long groupId = ContentUris.parseId(mChatURI); Uri uri = ContentUris.withAppendedId(Imps.GroupMembers.CONTENT_URI, groupId); mContentResolver.insert(uri, values); // insertMessageInDb(member.getName(), null, System.currentTimeMillis(), // Imps.MessageType.PRESENCE_AVAILABLE); } } void deleteGroupMemberInDb(Contact member) { String where = Imps.GroupMembers.USERNAME + "=?"; String[] selectionArgs = {member.getAddress().getAddress()}; if (mChatURI != null) { long groupId = ContentUris.parseId(mChatURI); Uri uri = ContentUris.withAppendedId(Imps.GroupMembers.CONTENT_URI, groupId); mContentResolver.delete(uri, where, selectionArgs); } // insertMessageInDb(member.getName(), null, System.currentTimeMillis(), // Imps.MessageType.PRESENCE_UNAVAILABLE); } void insertPresenceUpdatesMsg(String contact, Presence presence) { int status = presence.getStatus(); Integer previousStatus = mContactStatusMap.get(contact); if (previousStatus != null && previousStatus == status) { // don't insert the presence message if it's the same status // with the previous presence update notification return; } mContactStatusMap.put(contact, status); int messageType; switch (status) { case Presence.AVAILABLE: messageType = Imps.MessageType.PRESENCE_AVAILABLE; break; case Presence.AWAY: case Presence.IDLE: messageType = Imps.MessageType.PRESENCE_AWAY; break; case Presence.DO_NOT_DISTURB: messageType = Imps.MessageType.PRESENCE_DND; break; default: messageType = Imps.MessageType.PRESENCE_UNAVAILABLE; break; } if (mIsGroupChat) { insertMessageInDb(contact, null, System.currentTimeMillis(), messageType); } else { insertMessageInDb(null, null, System.currentTimeMillis(), messageType); } } void removeMessageInDb(int type) { mContentResolver.delete( mMessageURI, Imps.Messages.TYPE + "=?", new String[] {Integer.toString(type)}); } Uri insertMessageInDb(String contact, String body, long time, int type) { return insertMessageInDb(contact, body, time, type, 0 /*No error*/, nextID()); } /** A prefix helps to make sure that ID's are unique across mutliple instances. */ private static String prefix = StringUtils.randomString(5) + "-"; /** Keeps track of the current increment, which is appended to the prefix to forum a unique ID. */ private static long id = 0; static String nextID() { return prefix + Long.toString(id++); } Uri insertMessageInDb(String contact, String body, long time, int type, int errCode, String id) { boolean isEncrypted = true; try { isEncrypted = getDefaultOtrChatSession().isChatEncrypted(); } catch (RemoteException e) { // Leave it as encrypted so it gets stored in memory // FIXME(miron) } return Imps.insertMessageInDb( mContentResolver, mIsGroupChat, mContactId, isEncrypted, contact, body, time, type, errCode, id, null); } int updateMessageInDb(String id, int type, long time) { Uri.Builder builder = Imps.Messages.OTR_MESSAGES_CONTENT_URI_BY_PACKET_ID.buildUpon(); builder.appendPath(id); ContentValues values = new ContentValues(2); values.put(Imps.Messages.TYPE, type); values.put(Imps.Messages.THREAD_ID, mContactId); if (time != -1) values.put(Imps.Messages.DATE, time); int result = mContentResolver.update(builder.build(), values, null, null); if (result == 0) { builder = Imps.Messages.CONTENT_URI_MESSAGES_BY_PACKET_ID.buildUpon(); builder.appendPath(id); result = mContentResolver.update(builder.build(), values, null, null); } return result; } class ListenerAdapter implements MessageListener, GroupMemberListener, OtrEngineListener { public synchronized boolean onIncomingMessage( ChatSession ses, final org.awesomeapp.messenger.model.Message msg) { String body = msg.getBody(); String username = msg.getFrom().getAddress(); String bareUsername = msg.getFrom().getBareAddress(); String nickname = getNickName(username); long time = msg.getDateTime().getTime(); if (msg.getID() != null && Imps.messageExists(mContentResolver, msg.getID())) { return false; // this message is a duplicate } insertOrUpdateChat(body); boolean wasMessageSeen = false; if (msg.getID() == null) insertMessageInDb(nickname, body, time, msg.getType()); else insertMessageInDb(nickname, body, time, msg.getType(), 0, msg.getID()); int N = mRemoteListeners.beginBroadcast(); for (int i = 0; i < N; i++) { IChatListener listener = mRemoteListeners.getBroadcastItem(i); try { boolean wasSeen = listener.onIncomingMessage(ChatSessionAdapter.this, msg); if (wasSeen) wasMessageSeen = wasSeen; } catch (RemoteException e) { // The RemoteCallbackList will take care of removing the // dead listeners. } } mRemoteListeners.finishBroadcast(); // Due to the move to fragments, we could have listeners for ChatViews that are not visible on // the screen. // This is for fragments adjacent to the current one. Therefore we can't use the existence of // listeners // as a filter on notifications. if (!wasMessageSeen) { // reinstated body display here in the notification; perhaps add preferences to turn that // off mStatusBarNotifier.notifyChat( mConnection.getProviderId(), mConnection.getAccountId(), getId(), bareUsername, nickname, body, false); } mHasUnreadMessages = true; return true; } public void onSendMessageError( ChatSession ses, final org.awesomeapp.messenger.model.Message msg, final ImErrorInfo error) { insertMessageInDb( null, null, System.currentTimeMillis(), Imps.MessageType.OUTGOING, error.getCode(), null); final int N = mRemoteListeners.beginBroadcast(); for (int i = 0; i < N; i++) { IChatListener listener = mRemoteListeners.getBroadcastItem(i); try { listener.onSendMessageError(ChatSessionAdapter.this, msg, error); } catch (RemoteException e) { // The RemoteCallbackList will take care of removing the // dead listeners. } } mRemoteListeners.finishBroadcast(); } public void onSubjectChanged(ChatGroup group, String subject) { if (mChatURI != null) { ContentValues values1 = new ContentValues(1); values1.put(Imps.Contacts.NICKNAME, subject); ContentValues values = values1; Uri uriContact = ContentUris.withAppendedId(Imps.Contacts.CONTENT_URI, mContactId); mContentResolver.update(uriContact, values, null, null); // insertMessageInDb(member.getName(), null, System.currentTimeMillis(), // Imps.MessageType.PRESENCE_AVAILABLE); } } public void onMemberJoined(ChatGroup group, final Contact contact) { insertGroupMemberInDb(contact); final int N = mRemoteListeners.beginBroadcast(); for (int i = 0; i < N; i++) { IChatListener listener = mRemoteListeners.getBroadcastItem(i); try { listener.onContactJoined(ChatSessionAdapter.this, contact); } catch (RemoteException e) { // The RemoteCallbackList will take care of removing the // dead listeners. } } mRemoteListeners.finishBroadcast(); } public void onMemberLeft(ChatGroup group, final Contact contact) { deleteGroupMemberInDb(contact); final int N = mRemoteListeners.beginBroadcast(); for (int i = 0; i < N; i++) { IChatListener listener = mRemoteListeners.getBroadcastItem(i); try { listener.onContactLeft(ChatSessionAdapter.this, contact); } catch (RemoteException e) { // The RemoteCallbackList will take care of removing the // dead listeners. } } mRemoteListeners.finishBroadcast(); } public void onError(ChatGroup group, final ImErrorInfo error) { // TODO: insert an error message? final int N = mRemoteListeners.beginBroadcast(); for (int i = 0; i < N; i++) { IChatListener listener = mRemoteListeners.getBroadcastItem(i); try { listener.onInviteError(ChatSessionAdapter.this, error); } catch (RemoteException e) { // The RemoteCallbackList will take care of removing the // dead listeners. } } mRemoteListeners.finishBroadcast(); } public void notifyChatSessionConverted() { final int N = mRemoteListeners.beginBroadcast(); for (int i = 0; i < N; i++) { IChatListener listener = mRemoteListeners.getBroadcastItem(i); try { listener.onConvertedToGroupChat(ChatSessionAdapter.this); } catch (RemoteException e) { // The RemoteCallbackList will take care of removing the // dead listeners. } } mRemoteListeners.finishBroadcast(); } @Override public void onIncomingReceipt(ChatSession ses, String id) { Imps.updateConfirmInDb(mContentResolver, mContactId, id, true); synchronized (mRemoteListeners) { int N = mRemoteListeners.beginBroadcast(); for (int i = 0; i < N; i++) { IChatListener listener = mRemoteListeners.getBroadcastItem(i); try { listener.onIncomingReceipt(ChatSessionAdapter.this, id); } catch (RemoteException e) { // The RemoteCallbackList will take care of removing the // dead listeners. } } mRemoteListeners.finishBroadcast(); } } @Override public void onMessagePostponed(ChatSession ses, String id) { updateMessageInDb(id, Imps.MessageType.POSTPONED, -1); } @Override public void onReceiptsExpected(ChatSession ses, boolean isExpected) { // TODO } @Override public void sessionStatusChanged(SessionID sessionID) { if (sessionID .getRemoteUserId() .equals(mChatSession.getParticipant().getAddress().getAddress())) onStatusChanged(mChatSession, OtrChatManager.getInstance().getSessionStatus(sessionID)); } @Override public void onStatusChanged(ChatSession session, SessionStatus status) { final int N = mRemoteListeners.beginBroadcast(); for (int i = 0; i < N; i++) { IChatListener listener = mRemoteListeners.getBroadcastItem(i); try { listener.onStatusChanged(ChatSessionAdapter.this); } catch (RemoteException e) { // The RemoteCallbackList will take care of removing the // dead listeners. // TODO Auto-generated method stub } } mRemoteListeners.finishBroadcast(); mDataHandler.onOtrStatusChanged(status); if (status == SessionStatus.ENCRYPTED) { sendPostponedMessages(); } mLastSessionStatus = status; } @Override public void onIncomingDataRequest( ChatSession session, org.awesomeapp.messenger.model.Message msg, byte[] value) { mDataHandler.onIncomingRequest(msg.getFrom(), msg.getTo(), value); } @Override public void onIncomingDataResponse( ChatSession session, org.awesomeapp.messenger.model.Message msg, byte[] value) { mDataHandler.onIncomingResponse(msg.getFrom(), msg.getTo(), value); } @Override public void onIncomingTransferRequest(final Transfer transfer) {} } class ChatConvertor implements GroupListener, GroupMemberListener { private ChatGroupManager mGroupMgr; private String mGroupName; public ChatConvertor() { mGroupMgr = mConnection.mGroupManager; } public void convertToGroupChat(String nickname) { mGroupMgr.addGroupListener(this); mGroupName = "G" + System.currentTimeMillis(); try { mGroupMgr.createChatGroupAsync(mGroupName, nickname, nickname); } catch (Exception e) { e.printStackTrace(); } } public void onGroupCreated(ChatGroup group) { if (mGroupName.equalsIgnoreCase(group.getName())) { mGroupMgr.removeGroupListener(this); group.addMemberListener(this); mGroupMgr.inviteUserAsync(group, (Contact) mChatSession.getParticipant()); } } public void onMemberJoined(ChatGroup group, Contact contact) { if (mChatSession.getParticipant().equals(contact)) { onConvertToGroupChatSuccess(group); } mContactStatusMap.put(contact.getName(), contact.getPresence().getStatus()); } public void onSubjectChanged(ChatGroup group, String subject) {} public void onGroupDeleted(ChatGroup group) {} public void onGroupError(int errorType, String groupName, ImErrorInfo error) {} public void onJoinedGroup(ChatGroup group) {} public void onLeftGroup(ChatGroup group) {} public void onError(ChatGroup group, ImErrorInfo error) {} public void onMemberLeft(ChatGroup group, Contact contact) { mContactStatusMap.remove(contact.getName()); } } @Override public void setDataListener(IDataListener dataListener) throws RemoteException { mDataListener = dataListener; mDataHandler.setDataListener(mDataListener); } @Override public void setIncomingFileResponse(String transferForm, boolean acceptThis, boolean acceptAll) { mAcceptTransfer = acceptThis; mAcceptAllTransfer = acceptAll; mWaitingForResponse = false; mDataHandler.acceptTransfer(mLastFileUrl, transferForm); } class DataHandlerListenerImpl extends IDataListener.Stub { @Override public void onTransferComplete( boolean outgoing, String offerId, String from, String url, String mimeType, String filePath) { try { if (outgoing) { Imps.updateConfirmInDb(service.getContentResolver(), mContactId, offerId, true); } else { try { boolean isVerified = getDefaultOtrChatSession().isKeyVerified(from); int type = isVerified ? Imps.MessageType.INCOMING_ENCRYPTED_VERIFIED : Imps.MessageType.INCOMING_ENCRYPTED; insertOrUpdateChat(filePath); Uri messageUri = Imps.insertMessageInDb( service.getContentResolver(), mIsGroupChat, getId(), true, from, filePath, System.currentTimeMillis(), type, 0, offerId, mimeType); int percent = (int) (100); String[] path = url.split("/"); String sanitizedPath = SystemServices.sanitize(path[path.length - 1]); final int N = mRemoteListeners.beginBroadcast(); for (int i = 0; i < N; i++) { IChatListener listener = mRemoteListeners.getBroadcastItem(i); try { listener.onIncomingFileTransferProgress(sanitizedPath, percent); } catch (RemoteException e) { // The RemoteCallbackList will take care of removing the // dead listeners. } } mRemoteListeners.finishBroadcast(); if (N == 0) { String nickname = getNickName(from); mStatusBarNotifier.notifyChat( mConnection.getProviderId(), mConnection.getAccountId(), getId(), from, nickname, service.getString(R.string.file_notify_text, mimeType, nickname), false); } } catch (Exception e) { Log.e(ImApp.LOG_TAG, "Error updating file transfer progress", e); } } /** * if (mimeType != null && mimeType.startsWith("audio")) { MediaPlayer mp = new * MediaPlayer(); try { mp.setDataSource(file.getCanonicalPath()); * * <p>mp.prepare(); mp.start(); * * <p>} catch (IOException e) { // TODO Auto-generated catch block //e.printStackTrace(); } * } */ } catch (Exception e) { // mHandler.showAlert(service.getString(R.string.error_chat_file_transfer_title), // service.getString(R.string.error_chat_file_transfer_body)); OtrDebugLogger.log("error reading file", e); } } @Override public synchronized void onTransferFailed( boolean outgoing, String offerId, String from, String url, String reason) { String[] path = url.split("/"); String sanitizedPath = SystemServices.sanitize(path[path.length - 1]); final int N = mRemoteListeners.beginBroadcast(); for (int i = 0; i < N; i++) { IChatListener listener = mRemoteListeners.getBroadcastItem(i); try { listener.onIncomingFileTransferError(sanitizedPath, reason); } catch (RemoteException e) { // The RemoteCallbackList will take care of removing the // dead listeners. } } mRemoteListeners.finishBroadcast(); } @Override public synchronized void onTransferProgress( boolean outgoing, String offerId, String from, String url, float percentF) { int percent = (int) (100 * percentF); String[] path = url.split("/"); String sanitizedPath = SystemServices.sanitize(path[path.length - 1]); try { final int N = mRemoteListeners.beginBroadcast(); for (int i = 0; i < N; i++) { IChatListener listener = mRemoteListeners.getBroadcastItem(i); try { listener.onIncomingFileTransferProgress(sanitizedPath, percent); } catch (RemoteException e) { // The RemoteCallbackList will take care of removing the // dead listeners. } } } catch (Exception e) { Log.e(ImApp.LOG_TAG, "error broadcasting progress", e); } finally { mRemoteListeners.finishBroadcast(); } } private String mLastTransferFrom; private String mLastTransferUrl; public void checkLastTransferRequest() { if (mLastTransferFrom != null) { onTransferRequested( mLastTransferUrl, mLastTransferFrom, mLastTransferFrom, mLastTransferUrl); mLastTransferFrom = null; mLastTransferUrl = null; } } @Override public synchronized boolean onTransferRequested( String offerId, String from, String to, String transferUrl) { mAcceptTransfer = false; mWaitingForResponse = true; mLastFileUrl = transferUrl; if (mAcceptAllTransfer) { mAcceptTransfer = true; mWaitingForResponse = false; mLastTransferFrom = from; mLastTransferUrl = transferUrl; mDataHandler.acceptTransfer(mLastFileUrl, from); } else { try { final int N = mRemoteListeners.beginBroadcast(); if (N > 0) { for (int i = 0; i < N; i++) { IChatListener listener = mRemoteListeners.getBroadcastItem(i); try { listener.onIncomingFileTransfer(from, transferUrl); } catch (RemoteException e) { // The RemoteCallbackList will take care of removing the // dead listeners. } } } else { mLastTransferFrom = from; mLastTransferUrl = transferUrl; String nickname = getNickName(from); // reinstated body display here in the notification; perhaps add preferences to turn // that off mStatusBarNotifier.notifyChat( mConnection.getProviderId(), mConnection.getAccountId(), getId(), from, nickname, "Incoming file request", false); } } finally { mRemoteListeners.finishBroadcast(); } mAcceptTransfer = false; // for now, wait for the user callback } return mAcceptTransfer; } } public boolean sendPushWhitelistToken(@NonNull String token) { if (mConnection.getState() == ImConnection.SUSPENDED) { // TODO Is it possible to postpone a TLV message? e.g: insertMessageInDb with type POSTPONED return false; } // Whitelist tokens are intended for one recipient, for now if (isGroupChatSession()) return false; org.awesomeapp.messenger.model.Message msg = new org.awesomeapp.messenger.model.Message(""); msg.setFrom(mConnection.getLoginUser().getAddress()); msg.setType(Imps.MessageType.OUTGOING); mChatSession.sendPushWhitelistTokenAsync(msg, new String[] {token}); return true; } public void setContactTyping(Contact contact, boolean isTyping) { int N = mRemoteListeners.beginBroadcast(); for (int i = 0; i < N; i++) { IChatListener listener = mRemoteListeners.getBroadcastItem(i); try { listener.onContactTyping(ChatSessionAdapter.this, contact, isTyping); } catch (RemoteException e) { // The RemoteCallbackList will take care of removing the // dead listeners. } } mRemoteListeners.finishBroadcast(); } public void sendTypingStatus(boolean isTyping) { // mConnection.sendTypingStatus("fpp", isTyping); } }
/** * Handles XMPP transport session. * * @author Daniel Henninger * @author Mehmet Ecevit */ public class XMPPSession extends TransportSession<XMPPBuddy> { static Logger Log = Logger.getLogger(XMPPSession.class); /** * Create an XMPP Session instance. * * @param registration Registration information used for logging in. * @param jid JID associated with this session. * @param transport Transport instance associated with this session. * @param priority Priority of this session. */ public XMPPSession( Registration registration, JID jid, XMPPTransport transport, Integer priority) { super(registration, jid, transport, priority); setSupportedFeature(SupportedFeature.attention); setSupportedFeature(SupportedFeature.chatstates); Log.debug( "Creating " + getTransport().getType() + " session for " + registration.getUsername()); String connecthost; Integer connectport; String domain; connecthost = JiveGlobals.getProperty( "plugin.gateway." + getTransport().getType() + ".connecthost", (getTransport().getType().equals(TransportType.gtalk) ? "talk.google.com" : getTransport().getType().equals(TransportType.facebook) ? "chat.facebook.com" : "jabber.org")); connectport = JiveGlobals.getIntProperty( "plugin.gateway." + getTransport().getType() + ".connectport", 5222); if (getTransport().getType().equals(TransportType.gtalk)) { domain = "gmail.com"; } else if (getTransport().getType().equals(TransportType.facebook)) { // if (connecthost.equals("www.facebook.com")) { connecthost = "chat.facebook.com"; // } // if (connectport.equals(80)) { connectport = 5222; // } domain = "chat.facebook.com"; } else if (getTransport().getType().equals(TransportType.renren)) { connecthost = "talk.renren.com"; connectport = 5222; domain = "renren.com"; } else { domain = connecthost; } // For different domains other than 'gmail.com', which is given with Google Application services if (registration.getUsername().indexOf("@") > -1) { domain = registration.getUsername().substring(registration.getUsername().indexOf("@") + 1); } // If administrator specified "*" for domain, allow user to connect to anything. if (connecthost.equals("*")) { connecthost = domain; } config = new ConnectionConfiguration(connecthost, connectport, domain); config.setCompressionEnabled( JiveGlobals.getBooleanProperty( "plugin.gateway." + getTransport().getType() + ".usecompression", false)); if (getTransport().getType().equals(TransportType.facebook)) { // SASLAuthentication.supportSASLMechanism("PLAIN", 0); // config.setSASLAuthenticationEnabled(false); // config.setSecurityMode(ConnectionConfiguration.SecurityMode.enabled); } // instead, send the initial presence right after logging in. This // allows us to use a different presence mode than the plain old // 'available' as initial presence. config.setSendPresence(false); if (getTransport().getType().equals(TransportType.gtalk) && JiveGlobals.getBooleanProperty("plugin.gateway.gtalk.mailnotifications", true)) { ProviderManager.getInstance() .addIQProvider( GoogleMailBoxPacket.MAILBOX_ELEMENT, GoogleMailBoxPacket.MAILBOX_NAMESPACE, new GoogleMailBoxPacket.Provider()); ProviderManager.getInstance() .addExtensionProvider( GoogleNewMailExtension.ELEMENT_NAME, GoogleNewMailExtension.NAMESPACE, new GoogleNewMailExtension.Provider()); } } /* * XMPP connection */ public XMPPConnection conn = null; /** XMPP listener */ private XMPPListener listener = null; /** Run thread. */ private Thread runThread = null; /** Instance that will handle all presence stanzas sent from the legacy domain */ private XMPPPresenceHandler presenceHandler = null; /* * XMPP connection configuration */ private final ConnectionConfiguration config; /** Timer to check for online status. */ public Timer timer = new Timer(); /** Interval at which status is checked. */ private int timerInterval = 60000; // 1 minute /** Mail checker */ MailCheck mailCheck; /** XMPP Resource - the resource we are using (randomly generated) */ public String xmppResource = StringUtils.randomString(10); /** * Returns a full JID based off of a username passed in. * * <p>If it already looks like a JID, returns what was passed in. * * @param username Username to turn into a JID. * @return Converted username. */ public String generateFullJID(String username) { if (username.indexOf("@") > -1) { return username; } if (getTransport().getType().equals(TransportType.gtalk)) { return username + "@" + "gmail.com"; } else if (getTransport().getType().equals(TransportType.facebook)) { return username + "@" + "chat.facebook.com"; } else if (getTransport().getType().equals(TransportType.renren)) { return username + "@" + "renren.com"; } else if (getTransport().getType().equals(TransportType.livejournal)) { return username + "@" + "livejournal.com"; } else { String connecthost = JiveGlobals.getProperty( "plugin.gateway." + getTransport().getType() + ".connecthost", (getTransport().getType().equals(TransportType.gtalk) ? "talk.google.com" : getTransport().getType().equals(TransportType.facebook) ? "chat.facebook.com" : "jabber.org")); return username + "@" + connecthost; } } /** * Returns a username based off of a registered name (possible JID) passed in. * * <p>If it already looks like a username, returns what was passed in. * * @param regName Registered name to turn into a username. * @return Converted registered name. */ public String generateUsername(String regName) { if (regName.equals("{PLATFORM}")) { return JiveGlobals.getProperty("plugin.gateway.facebook.platform.apikey") + "|" + JiveGlobals.getProperty("plugin.gateway.facebook.platform.apisecret"); } else if (regName.indexOf("@") > -1) { if (getTransport().getType().equals(TransportType.gtalk)) { return regName; } else { return regName.substring(0, regName.indexOf("@")); } } else { if (getTransport().getType().equals(TransportType.gtalk)) { return regName + "@gmail.com"; } else { return regName; } } } /** @see net.sf.kraken.session.TransportSession#logIn(net.sf.kraken.type.PresenceType, String) */ @Override public void logIn(PresenceType presenceType, String verboseStatus) { final org.jivesoftware.smack.packet.Presence presence = new org.jivesoftware.smack.packet.Presence( org.jivesoftware.smack.packet.Presence.Type.available); if (JiveGlobals.getBooleanProperty( "plugin.gateway." + getTransport().getType() + ".avatars", true) && getAvatar() != null) { Avatar avatar = getAvatar(); // Same thing in this case, so lets go ahead and set them. avatar.setLegacyIdentifier(avatar.getXmppHash()); VCardUpdateExtension ext = new VCardUpdateExtension(); ext.setPhotoHash(avatar.getLegacyIdentifier()); presence.addExtension(ext); } final Presence.Mode pMode = ((XMPPTransport) getTransport()).convertGatewayStatusToXMPP(presenceType); if (pMode != null) { presence.setMode(pMode); } if (verboseStatus != null && verboseStatus.trim().length() > 0) { presence.setStatus(verboseStatus); } setPendingPresenceAndStatus(presenceType, verboseStatus); if (!this.isLoggedIn()) { listener = new XMPPListener(this); presenceHandler = new XMPPPresenceHandler(this); runThread = new Thread() { @Override public void run() { String userName = generateUsername(registration.getUsername()); conn = new XMPPConnection(config); try { conn.getSASLAuthentication() .registerSASLMechanism("DIGEST-MD5", MySASLDigestMD5Mechanism.class); if (getTransport().getType().equals(TransportType.facebook) && registration.getUsername().equals("{PLATFORM}")) { conn.getSASLAuthentication() .registerSASLMechanism( "X-FACEBOOK-PLATFORM", FacebookConnectSASLMechanism.class); conn.getSASLAuthentication().supportSASLMechanism("X-FACEBOOK-PLATFORM", 0); } Roster.setDefaultSubscriptionMode(SubscriptionMode.manual); conn.connect(); conn.addConnectionListener(listener); try { conn.addPacketListener( presenceHandler, new PacketTypeFilter(org.jivesoftware.smack.packet.Presence.class)); // Use this to filter out anything we don't care about conn.addPacketListener( listener, new OrFilter( new PacketTypeFilter(GoogleMailBoxPacket.class), new PacketExtensionFilter( GoogleNewMailExtension.ELEMENT_NAME, GoogleNewMailExtension.NAMESPACE))); conn.login(userName, registration.getPassword(), xmppResource); conn.sendPacket(presence); // send initial presence. conn.getChatManager().addChatListener(listener); conn.getRoster().addRosterListener(listener); if (JiveGlobals.getBooleanProperty( "plugin.gateway." + getTransport().getType() + ".avatars", !TransportType.facebook.equals(getTransport().getType())) && getAvatar() != null) { new Thread() { @Override public void run() { Avatar avatar = getAvatar(); VCard vCard = new VCard(); try { vCard.load(conn); vCard.setAvatar( Base64.decode(avatar.getImageData()), avatar.getMimeType()); vCard.save(conn); } catch (XMPPException e) { Log.debug("XMPP: Error while updating vcard for avatar change.", e); } catch (NotFoundException e) { Log.debug("XMPP: Unable to find avatar while setting initial.", e); } } }.start(); } setLoginStatus(TransportLoginStatus.LOGGED_IN); syncUsers(); if (getTransport().getType().equals(TransportType.gtalk) && JiveGlobals.getBooleanProperty( "plugin.gateway.gtalk.mailnotifications", true)) { conn.sendPacket( new IQWithPacketExtension( generateFullJID(getRegistration().getUsername()), new GoogleUserSettingExtension(null, true, null), IQ.Type.SET)); conn.sendPacket( new IQWithPacketExtension( generateFullJID(getRegistration().getUsername()), new GoogleMailNotifyExtension())); mailCheck = new MailCheck(); timer.schedule(mailCheck, timerInterval, timerInterval); } } catch (XMPPException e) { Log.debug( getTransport().getType() + " user's login/password does not appear to be correct: " + getRegistration().getUsername(), e); setFailureStatus(ConnectionFailureReason.USERNAME_OR_PASSWORD_INCORRECT); sessionDisconnectedNoReconnect( LocaleUtils.getLocalizedString("gateway.xmpp.passwordincorrect", "kraken")); } } catch (XMPPException e) { Log.debug( getTransport().getType() + " user is not able to connect: " + getRegistration().getUsername(), e); setFailureStatus(ConnectionFailureReason.CAN_NOT_CONNECT); sessionDisconnected( LocaleUtils.getLocalizedString("gateway.xmpp.connectionfailed", "kraken")); } } }; runThread.start(); } } /** @see net.sf.kraken.session.TransportSession#logOut() */ @Override public void logOut() { cleanUp(); sessionDisconnectedNoReconnect(null); } /** @see net.sf.kraken.session.TransportSession#cleanUp() */ @Override public void cleanUp() { if (timer != null) { try { timer.cancel(); } catch (Exception e) { // Ignore } timer = null; } if (mailCheck != null) { try { mailCheck.cancel(); } catch (Exception e) { // Ignore } mailCheck = null; } if (conn != null) { try { conn.removeConnectionListener(listener); } catch (Exception e) { // Ignore } try { conn.removePacketListener(listener); } catch (Exception e) { // Ignore } try { conn.removePacketListener(presenceHandler); } catch (Exception e) { // Ignore } try { conn.getChatManager().removeChatListener(listener); } catch (Exception e) { // Ignore } try { conn.getRoster().removeRosterListener(listener); } catch (Exception e) { // Ignore } try { conn.disconnect(); } catch (Exception e) { // Ignore } } conn = null; listener = null; presenceHandler = null; if (runThread != null) { try { runThread.interrupt(); } catch (Exception e) { // Ignore } runThread = null; } } /** * @see net.sf.kraken.session.TransportSession#updateStatus(net.sf.kraken.type.PresenceType, * String) */ @Override public void updateStatus(PresenceType presenceType, String verboseStatus) { setPresenceAndStatus(presenceType, verboseStatus); final org.jivesoftware.smack.packet.Presence presence = constructCurrentLegacyPresencePacket(); try { conn.sendPacket(presence); } catch (IllegalStateException e) { Log.debug("XMPP: Not connected while trying to change status."); } } /** * @see net.sf.kraken.session.TransportSession#addContact(org.xmpp.packet.JID, String, * java.util.ArrayList) */ @Override public void addContact(JID jid, String nickname, ArrayList<String> groups) { String mail = getTransport().convertJIDToID(jid); try { conn.getRoster().createEntry(mail, nickname, groups.toArray(new String[groups.size()])); RosterEntry entry = conn.getRoster().getEntry(mail); getBuddyManager() .storeBuddy(new XMPPBuddy(getBuddyManager(), mail, nickname, entry.getGroups(), entry)); } catch (XMPPException ex) { Log.debug("XMPP: unable to add:" + mail); } } /** * @see net.sf.kraken.session.TransportSession#removeContact(net.sf.kraken.roster.TransportBuddy) */ @Override public void removeContact(XMPPBuddy contact) { RosterEntry user2remove; String mail = getTransport().convertJIDToID(contact.getJID()); user2remove = conn.getRoster().getEntry(mail); try { conn.getRoster().removeEntry(user2remove); } catch (XMPPException ex) { Log.debug("XMPP: unable to remove:" + mail); } } /** * @see net.sf.kraken.session.TransportSession#updateContact(net.sf.kraken.roster.TransportBuddy) */ @Override public void updateContact(XMPPBuddy contact) { RosterEntry user2Update; String mail = getTransport().convertJIDToID(contact.getJID()); user2Update = conn.getRoster().getEntry(mail); user2Update.setName(contact.getNickname()); Collection<String> newgroups = contact.getGroups(); if (newgroups == null) { newgroups = new ArrayList<String>(); } for (RosterGroup group : conn.getRoster().getGroups()) { if (newgroups.contains(group.getName())) { if (!group.contains(user2Update)) { try { group.addEntry(user2Update); } catch (XMPPException e) { Log.debug("XMPP: Unable to add roster item to group."); } } newgroups.remove(group.getName()); } else { if (group.contains(user2Update)) { try { group.removeEntry(user2Update); } catch (XMPPException e) { Log.debug("XMPP: Unable to delete roster item from group."); } } } } for (String group : newgroups) { RosterGroup newgroup = conn.getRoster().createGroup(group); try { newgroup.addEntry(user2Update); } catch (XMPPException e) { Log.debug("XMPP: Unable to add roster item to new group."); } } } /** @see net.sf.kraken.session.TransportSession#acceptAddContact(JID) */ @Override public void acceptAddContact(JID jid) { final String userID = getTransport().convertJIDToID(jid); Log.debug("XMPP: accept-add contact: " + userID); final Presence accept = new Presence(Type.subscribed); accept.setTo(userID); conn.sendPacket(accept); } /** @see net.sf.kraken.session.TransportSession#sendMessage(org.xmpp.packet.JID, String) */ @Override public void sendMessage(JID jid, String message) { Chat chat = conn.getChatManager().createChat(getTransport().convertJIDToID(jid), listener); try { chat.sendMessage(message); } catch (XMPPException e) { // Ignore } } /** * @see net.sf.kraken.session.TransportSession#sendChatState(org.xmpp.packet.JID, * net.sf.kraken.type.ChatStateType) */ @Override public void sendChatState(JID jid, ChatStateType chatState) { final Presence presence = conn.getRoster().getPresence(jid.toString()); if (presence == null || presence.getType().equals(Presence.Type.unavailable)) { // don't send chat state to contacts that are offline. return; } Chat chat = conn.getChatManager().createChat(getTransport().convertJIDToID(jid), listener); try { ChatState state = ChatState.active; switch (chatState) { case active: state = ChatState.active; break; case composing: state = ChatState.composing; break; case paused: state = ChatState.paused; break; case inactive: state = ChatState.inactive; break; case gone: state = ChatState.gone; break; } Message message = new Message(); message.addExtension(new ChatStateExtension(state)); chat.sendMessage(message); } catch (XMPPException e) { // Ignore } } /** * @see net.sf.kraken.session.TransportSession#sendBuzzNotification(org.xmpp.packet.JID, String) */ @Override public void sendBuzzNotification(JID jid, String message) { Chat chat = conn.getChatManager().createChat(getTransport().convertJIDToID(jid), listener); try { Message m = new Message(); m.setTo(getTransport().convertJIDToID(jid)); m.addExtension(new BuzzExtension()); chat.sendMessage(m); } catch (XMPPException e) { // Ignore } } /** * Returns a (legacy/Smack-based) Presence stanza that represents the current presence of this * session. The Presence includes relevant Mode, Status and VCardUpdate information. * * <p>This method uses the fields {@link TransportSession#presence} and {@link * TransportSession#verboseStatus} to generate the result. * * @return A Presence packet representing the current presence state of this session. */ public Presence constructCurrentLegacyPresencePacket() { final org.jivesoftware.smack.packet.Presence presence = new org.jivesoftware.smack.packet.Presence( org.jivesoftware.smack.packet.Presence.Type.available); final Presence.Mode pMode = ((XMPPTransport) getTransport()).convertGatewayStatusToXMPP(this.presence); if (pMode != null) { presence.setMode(pMode); } if (verboseStatus != null && verboseStatus.trim().length() > 0) { presence.setStatus(verboseStatus); } final Avatar avatar = getAvatar(); if (avatar != null) { final VCardUpdateExtension ext = new VCardUpdateExtension(); ext.setPhotoHash(avatar.getLegacyIdentifier()); presence.addExtension(ext); } return presence; } /** @see net.sf.kraken.session.TransportSession#updateLegacyAvatar(String, byte[]) */ @Override public void updateLegacyAvatar(String type, final byte[] data) { new Thread() { @Override public void run() { Avatar avatar = getAvatar(); VCard vCard = new VCard(); try { vCard.load(conn); vCard.setAvatar(data, avatar.getMimeType()); vCard.save(conn); avatar.setLegacyIdentifier(avatar.getXmppHash()); // Same thing in this case, so lets go ahead and set them. final org.jivesoftware.smack.packet.Presence presence = constructCurrentLegacyPresencePacket(); conn.sendPacket(presence); } catch (XMPPException e) { Log.debug("XMPP: Error while updating vcard for avatar change.", e); } } }.start(); } private void syncUsers() { for (RosterEntry entry : conn.getRoster().getEntries()) { getBuddyManager() .storeBuddy( new XMPPBuddy( getBuddyManager(), entry.getUser(), entry.getName(), entry.getGroups(), entry)); // Facebook does not support presence probes in their XMPP implementation. See // http://developers.facebook.com/docs/chat#features if (!TransportType.facebook.equals(getTransport().getType())) { // ProbePacket probe = new ProbePacket(this.getJID()+"/"+xmppResource, entry.getUser()); ProbePacket probe = new ProbePacket(null, entry.getUser()); Log.debug("XMPP: Sending the following probe packet: " + probe.toXML()); try { conn.sendPacket(probe); } catch (IllegalStateException e) { Log.debug("XMPP: Not connected while trying to send probe."); } } } try { getTransport().syncLegacyRoster(getJID(), getBuddyManager().getBuddies()); } catch (UserNotFoundException ex) { Log.error("XMPP: User not found while syncing legacy roster: ", ex); } getBuddyManager().activate(); // lets repoll the roster since smack seems to get out of sync... // we'll let the roster listener take care of this though. conn.getRoster().reload(); } private class MailCheck extends TimerTask { /** Check GMail for new mail. */ @Override public void run() { if (getTransport().getType().equals(TransportType.gtalk) && JiveGlobals.getBooleanProperty("plugin.gateway.gtalk.mailnotifications", true)) { GoogleMailNotifyExtension gmne = new GoogleMailNotifyExtension(); gmne.setNewerThanTime(listener.getLastGMailThreadDate()); gmne.setNewerThanTid(listener.getLastGMailThreadId()); conn.sendPacket( new IQWithPacketExtension(generateFullJID(getRegistration().getUsername()), gmne)); } } } }
/** * Creates new account. * * @param user full or bare jid. * @param password * @param accountType xmpp account type can be replaced depend on server part. * @param syncable * @param storePassword * @param useOrbot * @return assigned account name. * @throws NetworkException if user or server part are invalid. */ public String addAccount( String user, String password, AccountType accountType, boolean syncable, boolean storePassword, boolean useOrbot) throws NetworkException { if (accountType.getProtocol().isOAuth()) { int index = 1; while (true) { user = String.valueOf(index); boolean found = false; for (AccountItem accountItem : accountItems.values()) if (accountItem .getConnectionSettings() .getServerName() .equals(accountType.getFirstServer()) && accountItem.getConnectionSettings().getUserName().equals(user)) { found = true; break; } if (!found) break; index++; } } if (user == null) throw new NetworkException(R.string.EMPTY_USER_NAME); if (user.indexOf("@") == -1) { if ("".equals(accountType.getFirstServer())) throw new NetworkException(R.string.EMPTY_SERVER_NAME); else user += "@" + accountType.getFirstServer(); } String serverName = StringUtils.parseServer(user); String userName = StringUtils.parseName(user); String resource = StringUtils.parseResource(user); String host = accountType.getHost(); int port = accountType.getPort(); boolean tlsRequired = accountType.isTLSRequired(); if (useOrbot) tlsRequired = true; if ("".equals(serverName)) { throw new NetworkException(R.string.EMPTY_SERVER_NAME); } else if (!accountType.isAllowServer() && !serverName.equals(accountType.getFirstServer())) throw new NetworkException(R.string.INCORRECT_USER_NAME); if ("".equals(userName)) throw new NetworkException(R.string.EMPTY_USER_NAME); if ("".equals(resource)) resource = "android" + StringUtils.randomString(8); if (accountType.getId() == R.array.account_type_xmpp) { host = serverName; for (AccountType check : accountTypes) if (check.getServers().contains(serverName)) { accountType = check; host = check.getHost(); port = check.getPort(); tlsRequired = check.isTLSRequired(); break; } } AccountItem accountItem; for (; ; ) { if (getAccount(userName + '@' + serverName + '/' + resource) == null) break; resource = "android" + StringUtils.randomString(8); } accountItem = addAccount( accountType.getProtocol(), true, host, port, serverName, userName, storePassword, password, resource, getNextColorIndex(), 0, StatusMode.available, SettingsManager.statusText(), true, true, tlsRequired ? TLSMode.required : TLSMode.enabled, false, useOrbot ? ProxyType.orbot : ProxyType.none, "localhost", 8080, "", "", syncable, null, null, ArchiveMode.available); onAccountChanged(accountItem.getAccount()); if (accountItems.size() > 1 && SettingsManager.contactsEnableShowAccounts()) SettingsManager.enableContactsShowAccount(); return accountItem.getAccount(); }
/** * A straightforward implementation of the basic instant messaging operation set. * * @author Damian Minkov * @author Matthieu Helleringer * @author Alain Knaebel * @author Emil Ivov * @author Hristo Terezov */ public class OperationSetBasicInstantMessagingJabberImpl extends AbstractOperationSetBasicInstantMessaging implements OperationSetMessageCorrection { /** Our class logger */ private static final Logger logger = Logger.getLogger(OperationSetBasicInstantMessagingJabberImpl.class); /** The maximum number of unread threads that we'd be notifying the user of. */ private static final String PNAME_MAX_GMAIL_THREADS_PER_NOTIFICATION = "net.java.sip.communicator.impl.protocol.jabber." + "MAX_GMAIL_THREADS_PER_NOTIFICATION"; /** * A table mapping contact addresses to full jids that can be used to target a specific resource * (rather than sending a message to all logged instances of a user). */ private Map<String, StoredThreadID> jids = new Hashtable<String, StoredThreadID>(); /** The most recent full JID used for the contact address. */ private Map<String, String> recentJIDForAddress = new Hashtable<String, String>(); /** * The smackMessageListener instance listens for incoming messages. Keep a reference of it so if * anything goes wrong we don't add two different instances. */ private SmackMessageListener smackMessageListener = null; /** * Contains the complete jid of a specific user and the time that it was last used so that we * could remove it after a certain point. */ public static class StoredThreadID { /** The time that we last sent or received a message from this jid */ long lastUpdatedTime; /** The last chat used, this way we will reuse the thread-id */ String threadID; } /** A prefix helps to make sure that thread ID's are unique across mutliple instances. */ private static String prefix = StringUtils.randomString(5); /** * Keeps track of the current increment, which is appended to the prefix to forum a unique thread * ID. */ private static long id = 0; /** * The number of milliseconds that we preserve threads with no traffic before considering them * dead. */ private static final long JID_INACTIVITY_TIMEOUT = 10 * 60 * 1000; // 10 min. /** * Indicates the time of the last Mailbox report that we received from Google (if this is a Google * server we are talking to). Should be included in all following mailbox queries */ private long lastReceivedMailboxResultTime = -1; /** The provider that created us. */ private final ProtocolProviderServiceJabberImpl jabberProvider; /** * A reference to the persistent presence operation set that we use to match incoming messages to * <tt>Contact</tt>s and vice versa. */ private OperationSetPersistentPresenceJabberImpl opSetPersPresence = null; /** The opening BODY HTML TAG: <body> */ private static final String OPEN_BODY_TAG = "<body>"; /** The closing BODY HTML TAG: <body> */ private static final String CLOSE_BODY_TAG = "</body>"; /** The html namespace used as feature XHTMLManager.namespace */ private static final String HTML_NAMESPACE = "http://jabber.org/protocol/xhtml-im"; /** List of filters to be used to filter which messages to handle current Operation Set. */ private List<PacketFilter> packetFilters = new ArrayList<PacketFilter>(); /** Whether carbon is enabled or not. */ private boolean isCarbonEnabled = false; /** * Creates an instance of this operation set. * * @param provider a reference to the <tt>ProtocolProviderServiceImpl</tt> that created us and * that we'll use for retrieving the underlying aim connection. */ OperationSetBasicInstantMessagingJabberImpl(ProtocolProviderServiceJabberImpl provider) { this.jabberProvider = provider; packetFilters.add(new GroupMessagePacketFilter()); packetFilters.add(new PacketTypeFilter(org.jivesoftware.smack.packet.Message.class)); provider.addRegistrationStateChangeListener(new RegistrationStateListener()); ProviderManager man = ProviderManager.getInstance(); MessageCorrectionExtensionProvider extProvider = new MessageCorrectionExtensionProvider(); man.addExtensionProvider( MessageCorrectionExtension.ELEMENT_NAME, MessageCorrectionExtension.NAMESPACE, extProvider); } /** * Create a Message instance with the specified UID, content type and a default encoding. This * method can be useful when message correction is required. One can construct the corrected * message to have the same UID as the message before correction. * * @param messageText the string content of the message. * @param contentType the MIME-type for <tt>content</tt> * @param messageUID the unique identifier of this message. * @return Message the newly created message */ public Message createMessageWithUID(String messageText, String contentType, String messageUID) { return new MessageJabberImpl(messageText, contentType, DEFAULT_MIME_ENCODING, null, messageUID); } /** * Create a Message instance for sending arbitrary MIME-encoding content. * * @param content content value * @param contentType the MIME-type for <tt>content</tt> * @return the newly created message. */ public Message createMessage(String content, String contentType) { return createMessage(content, contentType, DEFAULT_MIME_ENCODING, null); } /** * Create a Message instance for sending arbitrary MIME-encoding content. * * @param content content value * @param contentType the MIME-type for <tt>content</tt> * @param subject the Subject of the message that we'd like to create. * @param encoding the enconding of the message that we will be sending. * @return the newly created message. */ @Override public Message createMessage( String content, String contentType, String encoding, String subject) { return new MessageJabberImpl(content, contentType, encoding, subject); } Message createMessage(String content, String contentType, String messageUID) { return new MessageJabberImpl(content, contentType, DEFAULT_MIME_ENCODING, null, messageUID); } /** * Determines wheter the protocol provider (or the protocol itself) support sending and receiving * offline messages. Most often this method would return true for protocols that support offline * messages and false for those that don't. It is however possible for a protocol to support these * messages and yet have a particular account that does not (i.e. feature not enabled on the * protocol server). In cases like this it is possible for this method to return true even when * offline messaging is not supported, and then have the sendMessage method throw an * OperationFailedException with code - OFFLINE_MESSAGES_NOT_SUPPORTED. * * @return <tt>true</tt> if the protocol supports offline messages and <tt>false</tt> otherwise. */ public boolean isOfflineMessagingSupported() { return true; } /** * Determines wheter the protocol supports the supplied content type * * @param contentType the type we want to check * @return <tt>true</tt> if the protocol supports it and <tt>false</tt> otherwise. */ public boolean isContentTypeSupported(String contentType) { return (contentType.equals(DEFAULT_MIME_TYPE) || contentType.equals(HTML_MIME_TYPE)); } /** * Determines whether the protocol supports the supplied content type for the given contact. * * @param contentType the type we want to check * @param contact contact which is checked for supported contentType * @return <tt>true</tt> if the contact supports it and <tt>false</tt> otherwise. */ @Override public boolean isContentTypeSupported(String contentType, Contact contact) { // by default we support default mime type, for other mimetypes // method must be overriden if (contentType.equals(DEFAULT_MIME_TYPE)) return true; else if (contentType.equals(HTML_MIME_TYPE)) { String toJID = recentJIDForAddress.get(contact.getAddress()); if (toJID == null) toJID = contact.getAddress(); return jabberProvider.isFeatureListSupported(toJID, HTML_NAMESPACE); } return false; } /** * Remove from our <tt>jids</tt> map all entries that have not seen any activity (i.e. neither * outgoing nor incoming messags) for more than JID_INACTIVITY_TIMEOUT. Note that this method is * not synchronous and that it is only meant for use by the {@link #getThreadIDForAddress(String)} * and {@link #putJidForAddress(String, String)} */ private void purgeOldJids() { long currentTime = System.currentTimeMillis(); Iterator<Map.Entry<String, StoredThreadID>> entries = jids.entrySet().iterator(); while (entries.hasNext()) { Map.Entry<String, StoredThreadID> entry = entries.next(); StoredThreadID target = entry.getValue(); if (currentTime - target.lastUpdatedTime > JID_INACTIVITY_TIMEOUT) entries.remove(); } } /** * Returns the last jid that the party with the specified <tt>address</tt> contacted us from or * <tt>null</tt>(or bare jid) if we don't have a jid for the specified <tt>address</tt> yet. The * method would also purge all entries that haven't seen any activity (i.e. no one has tried to * get or remap it) for a delay longer than <tt>JID_INACTIVITY_TIMEOUT</tt>. * * @param jid the <tt>jid</tt> that we'd like to obtain a threadID for. * @return the last jid that the party with the specified <tt>address</tt> contacted us from or * <tt>null</tt> if we don't have a jid for the specified <tt>address</tt> yet. */ String getThreadIDForAddress(String jid) { synchronized (jids) { purgeOldJids(); StoredThreadID ta = jids.get(jid); if (ta == null) return null; ta.lastUpdatedTime = System.currentTimeMillis(); return ta.threadID; } } /** * Maps the specified <tt>address</tt> to <tt>jid</tt>. The point of this method is to allow us to * send all messages destined to the contact with the specified <tt>address</tt> to the * <tt>jid</tt> that they last contacted us from. * * @param threadID the threadID of conversation. * @param jid the jid (i.e. address/resource) that the contact with the specified <tt>address</tt> * last contacted us from. */ private void putJidForAddress(String jid, String threadID) { synchronized (jids) { purgeOldJids(); StoredThreadID ta = jids.get(jid); if (ta == null) { ta = new StoredThreadID(); jids.put(jid, ta); } recentJIDForAddress.put(StringUtils.parseBareAddress(jid), jid); ta.lastUpdatedTime = System.currentTimeMillis(); ta.threadID = threadID; } } /** * Helper function used to send a message to a contact, with the given extensions attached. * * @param to The contact to send the message to. * @param toResource The resource to send the message to or null if no resource has been specified * @param message The message to send. * @param extensions The XMPP extensions that should be attached to the message before sending. * @return The MessageDeliveryEvent that resulted after attempting to send this message, so the * calling function can modify it if needed. */ private MessageDeliveredEvent sendMessage( Contact to, ContactResource toResource, Message message, PacketExtension[] extensions) { if (!(to instanceof ContactJabberImpl)) throw new IllegalArgumentException("The specified contact is not a Jabber contact." + to); assertConnected(); org.jivesoftware.smack.packet.Message msg = new org.jivesoftware.smack.packet.Message(); String toJID = null; if (toResource != null) { if (toResource.equals(ContactResource.BASE_RESOURCE)) { toJID = to.getAddress(); } else toJID = ((ContactResourceJabberImpl) toResource).getFullJid(); } if (toJID == null) { toJID = to.getAddress(); } msg.setPacketID(message.getMessageUID()); msg.setTo(toJID); for (PacketExtension ext : extensions) { msg.addExtension(ext); } if (logger.isTraceEnabled()) logger.trace("Will send a message to:" + toJID + " chat.jid=" + toJID); MessageDeliveredEvent msgDeliveryPendingEvt = new MessageDeliveredEvent(message, to, toResource); MessageDeliveredEvent[] transformedEvents = messageDeliveryPendingTransform(msgDeliveryPendingEvt); if (transformedEvents == null || transformedEvents.length == 0) return null; for (MessageDeliveredEvent event : transformedEvents) { String content = event.getSourceMessage().getContent(); if (message.getContentType().equals(HTML_MIME_TYPE)) { msg.setBody(Html2Text.extractText(content)); // Check if the other user supports XHTML messages // make sure we use our discovery manager as it caches calls if (jabberProvider.isFeatureListSupported(toJID, HTML_NAMESPACE)) { // Add the XHTML text to the message XHTMLManager.addBody(msg, OPEN_BODY_TAG + content + CLOSE_BODY_TAG); } } else { // this is plain text so keep it as it is. msg.setBody(content); } // msg.addExtension(new Version()); if (event.isMessageEncrypted() && isCarbonEnabled) { msg.addExtension(new CarbonPacketExtension.PrivateExtension()); } MessageEventManager.addNotificationsRequests(msg, true, false, false, true); String threadID = getThreadIDForAddress(toJID); if (threadID == null) threadID = nextThreadID(); msg.setThread(threadID); msg.setType(org.jivesoftware.smack.packet.Message.Type.chat); msg.setFrom(jabberProvider.getConnection().getUser()); jabberProvider.getConnection().sendPacket(msg); putJidForAddress(toJID, threadID); } return new MessageDeliveredEvent(message, to, toResource); } /** * Sends the <tt>message</tt> to the destination indicated by the <tt>to</tt> contact. * * @param to the <tt>Contact</tt> to send <tt>message</tt> to * @param message the <tt>Message</tt> to send. * @throws java.lang.IllegalStateException if the underlying stack is not registered and * initialized. * @throws java.lang.IllegalArgumentException if <tt>to</tt> is not an instance of ContactImpl. */ public void sendInstantMessage(Contact to, Message message) throws IllegalStateException, IllegalArgumentException { sendInstantMessage(to, null, message); } /** * Sends the <tt>message</tt> to the destination indicated by the <tt>to</tt>. Provides a default * implementation of this method. * * @param to the <tt>Contact</tt> to send <tt>message</tt> to * @param toResource the resource to which the message should be send * @param message the <tt>Message</tt> to send. * @throws java.lang.IllegalStateException if the underlying ICQ stack is not registered and * initialized. * @throws java.lang.IllegalArgumentException if <tt>to</tt> is not an instance belonging to the * underlying implementation. */ @Override public void sendInstantMessage(Contact to, ContactResource toResource, Message message) throws IllegalStateException, IllegalArgumentException { MessageDeliveredEvent msgDelivered = sendMessage(to, toResource, message, new PacketExtension[0]); fireMessageEvent(msgDelivered); } /** * Replaces the message with ID <tt>correctedMessageUID</tt> sent to the contact <tt>to</tt> with * the message <tt>message</tt> * * @param to The contact to send the message to. * @param message The new message. * @param correctedMessageUID The ID of the message being replaced. */ public void correctMessage( Contact to, ContactResource resource, Message message, String correctedMessageUID) { PacketExtension[] exts = new PacketExtension[1]; exts[0] = new MessageCorrectionExtension(correctedMessageUID); MessageDeliveredEvent msgDelivered = sendMessage(to, resource, message, exts); msgDelivered.setCorrectedMessageUID(correctedMessageUID); fireMessageEvent(msgDelivered); } /** * Utility method throwing an exception if the stack is not properly initialized. * * @throws java.lang.IllegalStateException if the underlying stack is not registered and * initialized. */ private void assertConnected() throws IllegalStateException { if (opSetPersPresence == null) { throw new IllegalStateException( "The provider must be signed on the service before" + " being able to communicate."); } else opSetPersPresence.assertConnected(); } /** Our listener that will tell us when we're registered to */ private class RegistrationStateListener implements RegistrationStateChangeListener { /** * The method is called by a ProtocolProvider implementation whenever a change in the * registration state of the corresponding provider had occurred. * * @param evt ProviderStatusChangeEvent the event describing the status change. */ public void registrationStateChanged(RegistrationStateChangeEvent evt) { if (logger.isDebugEnabled()) logger.debug( "The provider changed state from: " + evt.getOldState() + " to: " + evt.getNewState()); if (evt.getNewState() == RegistrationState.REGISTERING) { opSetPersPresence = (OperationSetPersistentPresenceJabberImpl) jabberProvider.getOperationSet(OperationSetPersistentPresence.class); if (smackMessageListener == null) { smackMessageListener = new SmackMessageListener(); } else { // make sure this listener is not already installed in this // connection jabberProvider.getConnection().removePacketListener(smackMessageListener); } jabberProvider .getConnection() .addPacketListener( smackMessageListener, new AndFilter(packetFilters.toArray(new PacketFilter[packetFilters.size()]))); } else if (evt.getNewState() == RegistrationState.REGISTERED) { new Thread( new Runnable() { @Override public void run() { initAdditionalServices(); } }) .start(); } else if (evt.getNewState() == RegistrationState.UNREGISTERED || evt.getNewState() == RegistrationState.CONNECTION_FAILED || evt.getNewState() == RegistrationState.AUTHENTICATION_FAILED) { if (jabberProvider.getConnection() != null) { if (smackMessageListener != null) jabberProvider.getConnection().removePacketListener(smackMessageListener); } smackMessageListener = null; } } } /** Initialize additional services, like gmail notifications and message carbons. */ private void initAdditionalServices() { // subscribe for Google (Gmail or Google Apps) notifications // for new mail messages. boolean enableGmailNotifications = jabberProvider .getAccountID() .getAccountPropertyBoolean("GMAIL_NOTIFICATIONS_ENABLED", false); if (enableGmailNotifications) subscribeForGmailNotifications(); boolean enableCarbon = isCarbonSupported() && !jabberProvider .getAccountID() .getAccountPropertyBoolean(ProtocolProviderFactory.IS_CARBON_DISABLED, false); if (enableCarbon) { enableDisableCarbon(true); } else { isCarbonEnabled = false; } } /** * Sends enable or disable carbon packet to the server. * * @param enable if <tt>true</tt> sends enable packet otherwise sends disable packet. */ private void enableDisableCarbon(final boolean enable) { IQ iq = new IQ() { @Override public String getChildElementXML() { return "<" + (enable ? "enable" : "disable") + " xmlns='urn:xmpp:carbons:2' />"; } }; Packet response = null; try { PacketCollector packetCollector = jabberProvider .getConnection() .createPacketCollector(new PacketIDFilter(iq.getPacketID())); iq.setFrom(jabberProvider.getOurJID()); iq.setType(IQ.Type.SET); jabberProvider.getConnection().sendPacket(iq); response = packetCollector.nextResult(SmackConfiguration.getPacketReplyTimeout()); packetCollector.cancel(); } catch (Exception e) { logger.error("Failed to enable carbon.", e); } isCarbonEnabled = false; if (response == null) { logger.error("Failed to enable carbon. No response is received."); } else if (response.getError() != null) { logger.error("Failed to enable carbon: " + response.getError()); } else if (!(response instanceof IQ) || !((IQ) response).getType().equals(IQ.Type.RESULT)) { logger.error("Failed to enable carbon. The response is not correct."); } else { isCarbonEnabled = true; } } /** * Checks whether the carbon is supported by the server or not. * * @return <tt>true</tt> if carbon is supported by the server and <tt>false</tt> if not. */ private boolean isCarbonSupported() { try { return jabberProvider .getDiscoveryManager() .discoverInfo(jabberProvider.getAccountID().getService()) .containsFeature(CarbonPacketExtension.NAMESPACE); } catch (XMPPException e) { logger.warn("Failed to retrieve carbon support." + e.getMessage()); } return false; } /** The listener that we use in order to handle incoming messages. */ @SuppressWarnings("unchecked") private class SmackMessageListener implements PacketListener { /** * Handles incoming messages and dispatches whatever events are necessary. * * @param packet the packet that we need to handle (if it is a message). */ public void processPacket(Packet packet) { if (!(packet instanceof org.jivesoftware.smack.packet.Message)) return; org.jivesoftware.smack.packet.Message msg = (org.jivesoftware.smack.packet.Message) packet; boolean isForwardedSentMessage = false; if (msg.getBody() == null) { CarbonPacketExtension carbonExt = (CarbonPacketExtension) msg.getExtension(CarbonPacketExtension.NAMESPACE); if (carbonExt == null) return; isForwardedSentMessage = (carbonExt.getElementName() == CarbonPacketExtension.SENT_ELEMENT_NAME); List<ForwardedPacketExtension> extensions = carbonExt.getChildExtensionsOfType(ForwardedPacketExtension.class); if (extensions.isEmpty()) return; ForwardedPacketExtension forwardedExt = extensions.get(0); msg = forwardedExt.getMessage(); if (msg == null || msg.getBody() == null) return; } Object multiChatExtension = msg.getExtension("x", "http://jabber.org/protocol/muc#user"); // its not for us if (multiChatExtension != null) return; String userFullId = isForwardedSentMessage ? msg.getTo() : msg.getFrom(); String userBareID = StringUtils.parseBareAddress(userFullId); boolean isPrivateMessaging = false; ChatRoom privateContactRoom = null; OperationSetMultiUserChatJabberImpl mucOpSet = (OperationSetMultiUserChatJabberImpl) jabberProvider.getOperationSet(OperationSetMultiUserChat.class); if (mucOpSet != null) privateContactRoom = mucOpSet.getChatRoom(userBareID); if (privateContactRoom != null) { isPrivateMessaging = true; } if (logger.isDebugEnabled()) { if (logger.isDebugEnabled()) logger.debug("Received from " + userBareID + " the message " + msg.toXML()); } Message newMessage = createMessage(msg.getBody(), DEFAULT_MIME_TYPE, msg.getPacketID()); // check if the message is available in xhtml PacketExtension ext = msg.getExtension("http://jabber.org/protocol/xhtml-im"); if (ext != null) { XHTMLExtension xhtmlExt = (XHTMLExtension) ext; // parse all bodies Iterator<String> bodies = xhtmlExt.getBodies(); StringBuffer messageBuff = new StringBuffer(); while (bodies.hasNext()) { String body = bodies.next(); messageBuff.append(body); } if (messageBuff.length() > 0) { // we remove body tags around message cause their // end body tag is breaking // the visualization as html in the UI String receivedMessage = messageBuff .toString() // removes body start tag .replaceAll("\\<[bB][oO][dD][yY].*?>", "") // removes body end tag .replaceAll("\\</[bB][oO][dD][yY].*?>", ""); // for some reason ' is not rendered correctly // from our ui, lets use its equivalent. Other // similar chars(< > & ") seem ok. receivedMessage = receivedMessage.replaceAll("'", "'"); newMessage = createMessage(receivedMessage, HTML_MIME_TYPE, msg.getPacketID()); } } PacketExtension correctionExtension = msg.getExtension(MessageCorrectionExtension.NAMESPACE); String correctedMessageUID = null; if (correctionExtension != null) { correctedMessageUID = ((MessageCorrectionExtension) correctionExtension).getCorrectedMessageUID(); } Contact sourceContact = opSetPersPresence.findContactByID((isPrivateMessaging ? userFullId : userBareID)); if (msg.getType() == org.jivesoftware.smack.packet.Message.Type.error) { // error which is multichat and we don't know about the contact // is a muc message error which is missing muc extension // and is coming from the room, when we try to send message to // room which was deleted or offline on the server if (isPrivateMessaging && sourceContact == null) { if (privateContactRoom != null) { XMPPError error = packet.getError(); int errorResultCode = ChatRoomMessageDeliveryFailedEvent.UNKNOWN_ERROR; if (error != null && error.getCode() == 403) { errorResultCode = ChatRoomMessageDeliveryFailedEvent.FORBIDDEN; } String errorReason = error.getMessage(); ChatRoomMessageDeliveryFailedEvent evt = new ChatRoomMessageDeliveryFailedEvent( privateContactRoom, null, errorResultCode, errorReason, new Date(), newMessage); ((ChatRoomJabberImpl) privateContactRoom).fireMessageEvent(evt); } return; } if (logger.isInfoEnabled()) logger.info("Message error received from " + userBareID); int errorResultCode = MessageDeliveryFailedEvent.UNKNOWN_ERROR; if (packet.getError() != null) { int errorCode = packet.getError().getCode(); if (errorCode == 503) { org.jivesoftware.smackx.packet.MessageEvent msgEvent = (org.jivesoftware.smackx.packet.MessageEvent) packet.getExtension("x", "jabber:x:event"); if (msgEvent != null && msgEvent.isOffline()) { errorResultCode = MessageDeliveryFailedEvent.OFFLINE_MESSAGES_NOT_SUPPORTED; } } } if (sourceContact == null) { sourceContact = opSetPersPresence.createVolatileContact(userFullId, isPrivateMessaging); } MessageDeliveryFailedEvent ev = new MessageDeliveryFailedEvent( newMessage, sourceContact, correctedMessageUID, errorResultCode); // ev = messageDeliveryFailedTransform(ev); if (ev != null) fireMessageEvent(ev); return; } putJidForAddress(userFullId, msg.getThread()); // In the second condition we filter all group chat messages, // because they are managed by the multi user chat operation set. if (sourceContact == null) { if (logger.isDebugEnabled()) logger.debug("received a message from an unknown contact: " + userBareID); // create the volatile contact sourceContact = opSetPersPresence.createVolatileContact(userFullId, isPrivateMessaging); } Date timestamp = new Date(); // Check for XEP-0091 timestamp (deprecated) PacketExtension delay = msg.getExtension("x", "jabber:x:delay"); if (delay != null && delay instanceof DelayInformation) { timestamp = ((DelayInformation) delay).getStamp(); } // check for XEP-0203 timestamp delay = msg.getExtension("delay", "urn:xmpp:delay"); if (delay != null && delay instanceof DelayInfo) { timestamp = ((DelayInfo) delay).getStamp(); } ContactResource resource = ((ContactJabberImpl) sourceContact).getResourceFromJid(userFullId); EventObject msgEvt = null; if (!isForwardedSentMessage) msgEvt = new MessageReceivedEvent( newMessage, sourceContact, resource, timestamp, correctedMessageUID, isPrivateMessaging, privateContactRoom); else msgEvt = new MessageDeliveredEvent(newMessage, sourceContact, timestamp); // msgReceivedEvt = messageReceivedTransform(msgReceivedEvt); if (msgEvt != null) fireMessageEvent(msgEvt); } } /** A filter that prevents this operation set from handling multi user chat messages. */ private static class GroupMessagePacketFilter implements PacketFilter { /** * Returns <tt>true</tt> if <tt>packet</tt> is a <tt>Message</tt> and false otherwise. * * @param packet the packet that we need to check. * @return <tt>true</tt> if <tt>packet</tt> is a <tt>Message</tt> and false otherwise. */ public boolean accept(Packet packet) { if (!(packet instanceof org.jivesoftware.smack.packet.Message)) return false; org.jivesoftware.smack.packet.Message msg = (org.jivesoftware.smack.packet.Message) packet; return !msg.getType().equals(org.jivesoftware.smack.packet.Message.Type.groupchat); } } /** * Subscribes this provider as interested in receiving notifications for new mail messages from * Google mail services such as Gmail or Google Apps. */ private void subscribeForGmailNotifications() { // first check support for the notification service String accountIDService = jabberProvider.getAccountID().getService(); boolean notificationsAreSupported = jabberProvider.isFeatureSupported(accountIDService, NewMailNotificationIQ.NAMESPACE); if (!notificationsAreSupported) { if (logger.isDebugEnabled()) logger.debug( accountIDService + " does not seem to provide a Gmail notification " + " service so we won't be trying to subscribe for it"); return; } if (logger.isDebugEnabled()) logger.debug( accountIDService + " seems to provide a Gmail notification " + " service so we will try to subscribe for it"); ProviderManager providerManager = ProviderManager.getInstance(); providerManager.addIQProvider( MailboxIQ.ELEMENT_NAME, MailboxIQ.NAMESPACE, new MailboxIQProvider()); providerManager.addIQProvider( NewMailNotificationIQ.ELEMENT_NAME, NewMailNotificationIQ.NAMESPACE, new NewMailNotificationProvider()); Connection connection = jabberProvider.getConnection(); connection.addPacketListener(new MailboxIQListener(), new PacketTypeFilter(MailboxIQ.class)); connection.addPacketListener( new NewMailNotificationListener(), new PacketTypeFilter(NewMailNotificationIQ.class)); if (opSetPersPresence.getCurrentStatusMessage().equals(JabberStatusEnum.OFFLINE)) return; // create a query with -1 values for newer-than-tid and // newer-than-time attributes MailboxQueryIQ mailboxQuery = new MailboxQueryIQ(); if (logger.isTraceEnabled()) logger.trace( "sending mailNotification for acc: " + jabberProvider.getAccountID().getAccountUniqueID()); jabberProvider.getConnection().sendPacket(mailboxQuery); } /** * Creates an html description of the specified mailbox. * * @param mailboxIQ the mailboxIQ that we are to describe. * @return an html description of <tt>mailboxIQ</tt> */ private String createMailboxDescription(MailboxIQ mailboxIQ) { int threadCount = mailboxIQ.getThreadCount(); String resourceHeaderKey = threadCount > 1 ? "service.gui.NEW_GMAIL_MANY_HEADER" : "service.gui.NEW_GMAIL_HEADER"; String resourceFooterKey = threadCount > 1 ? "service.gui.NEW_GMAIL_MANY_FOOTER" : "service.gui.NEW_GMAIL_FOOTER"; // FIXME Escape HTML! String newMailHeader = JabberActivator.getResources() .getI18NString( resourceHeaderKey, new String[] { jabberProvider.getAccountID().getService(), // {0} - service name mailboxIQ.getUrl(), // {1} - inbox URI Integer.toString(threadCount) // {2} - thread count }); StringBuilder message = new StringBuilder(newMailHeader); // we now start an html table for the threads. message.append("<table width=100% cellpadding=2 cellspacing=0 "); message.append("border=0 bgcolor=#e8eef7>"); Iterator<MailThreadInfo> threads = mailboxIQ.threads(); String maxThreadsStr = (String) JabberActivator.getConfigurationService() .getProperty(PNAME_MAX_GMAIL_THREADS_PER_NOTIFICATION); int maxThreads = 5; try { if (maxThreadsStr != null) maxThreads = Integer.parseInt(maxThreadsStr); } catch (NumberFormatException e) { if (logger.isDebugEnabled()) logger.debug("Failed to parse max threads count: " + maxThreads + ". Going for default."); } // print a maximum of MAX_THREADS for (int i = 0; i < maxThreads && threads.hasNext(); i++) { message.append(threads.next().createHtmlDescription()); } message.append("</table><br/>"); if (threadCount > maxThreads) { String messageFooter = JabberActivator.getResources() .getI18NString( resourceFooterKey, new String[] { mailboxIQ.getUrl(), // {0} - inbox URI Integer.toString(threadCount - maxThreads) // {1} - thread count }); message.append(messageFooter); } return message.toString(); } public String getRecentJIDForAddress(String address) { return recentJIDForAddress.get(address); } /** Receives incoming MailNotification Packets */ private class MailboxIQListener implements PacketListener { /** * Handles incoming <tt>MailboxIQ</tt> packets. * * @param packet the IQ that we need to handle in case it is a <tt>MailboxIQ</tt>. */ public void processPacket(Packet packet) { if (packet != null && !(packet instanceof MailboxIQ)) return; MailboxIQ mailboxIQ = (MailboxIQ) packet; if (mailboxIQ.getTotalMatched() < 1) return; // Get a reference to a dummy volatile contact Contact sourceContact = opSetPersPresence.findContactByID(jabberProvider.getAccountID().getService()); if (sourceContact == null) sourceContact = opSetPersPresence.createVolatileContact(jabberProvider.getAccountID().getService()); lastReceivedMailboxResultTime = mailboxIQ.getResultTime(); String newMail = createMailboxDescription(mailboxIQ); Message newMailMessage = new MessageJabberImpl(newMail, HTML_MIME_TYPE, DEFAULT_MIME_ENCODING, null); MessageReceivedEvent msgReceivedEvt = new MessageReceivedEvent( newMailMessage, sourceContact, new Date(), MessageReceivedEvent.SYSTEM_MESSAGE_RECEIVED); fireMessageEvent(msgReceivedEvt); } } /** Receives incoming NewMailNotification Packets. */ private class NewMailNotificationListener implements PacketListener { /** * Handles incoming <tt>NewMailNotificationIQ</tt> packets. * * @param packet the IQ that we need to handle in case it is a <tt>NewMailNotificationIQ</tt>. */ public void processPacket(Packet packet) { if (packet != null && !(packet instanceof NewMailNotificationIQ)) return; // check whether we are still enabled. boolean enableGmailNotifications = jabberProvider .getAccountID() .getAccountPropertyBoolean("GMAIL_NOTIFICATIONS_ENABLED", false); if (!enableGmailNotifications) return; if (opSetPersPresence.getCurrentStatusMessage().equals(JabberStatusEnum.OFFLINE)) return; MailboxQueryIQ mailboxQueryIQ = new MailboxQueryIQ(); if (lastReceivedMailboxResultTime != -1) mailboxQueryIQ.setNewerThanTime(lastReceivedMailboxResultTime); if (logger.isTraceEnabled()) logger.trace( "send mailNotification for acc: " + jabberProvider.getAccountID().getAccountUniqueID()); jabberProvider.getConnection().sendPacket(mailboxQueryIQ); } } /** * Returns the inactivity timeout in milliseconds. * * @return The inactivity timeout in milliseconds. Or -1 if undefined */ public long getInactivityTimeout() { return JID_INACTIVITY_TIMEOUT; } /** * Adds additional filters for incoming messages. To be able to skip some messages. * * @param filter to add */ public void addMessageFilters(PacketFilter filter) { this.packetFilters.add(filter); } /** * Returns the next unique thread id. Each thread id made up of a short alphanumeric prefix along * with a unique numeric value. * * @return the next thread id. */ public static synchronized String nextThreadID() { return prefix + Long.toString(id++); } }
/** * Process the AdHoc-Command packet that request the execution of some action of a command. If * this is the first request, this method checks, before executing the command, if: * * <ul> * <li>The requested command exists * <li>The requester has permissions to execute it * <li>The command has more than one stage, if so, it saves the command and session ID for * further use * </ul> * * <br> * <br> * If this is not the first request, this method checks, before executing the command, if: * * <ul> * <li>The session ID of the request was stored * <li>The session life do not exceed the time out * <li>The action to execute is one of the available actions * </ul> * * @param requestData the packet to process. */ private void processAdHocCommand(AdHocCommandData requestData) { // Only process requests of type SET if (requestData.getType() != IQ.Type.SET) { return; } // Creates the response with the corresponding data AdHocCommandData response = new AdHocCommandData(); response.setTo(requestData.getFrom()); response.setPacketID(requestData.getPacketID()); response.setNode(requestData.getNode()); response.setId(requestData.getTo()); String sessionId = requestData.getSessionID(); String commandNode = requestData.getNode(); if (sessionId == null) { // A new execution request has been received. Check that the // command exists if (!commands.containsKey(commandNode)) { // Requested command does not exist so return // item_not_found error. respondError(response, XMPPError.Condition.item_not_found); return; } // Create new session ID sessionId = StringUtils.randomString(15); try { // Create a new instance of the command with the // corresponding sessioid LocalCommand command = newInstanceOfCmd(commandNode, sessionId); response.setType(IQ.Type.RESULT); command.setData(response); // Check that the requester has enough permission. // Answer forbidden error if requester permissions are not // enough to execute the requested command if (!command.hasPermission(requestData.getFrom())) { respondError(response, XMPPError.Condition.forbidden); return; } Action action = requestData.getAction(); // If the action is unknown then respond an error. if (action != null && action.equals(Action.unknown)) { respondError( response, XMPPError.Condition.bad_request, AdHocCommand.SpecificErrorCondition.malformedAction); return; } // If the action is not execute, then it is an invalid action. if (action != null && !action.equals(Action.execute)) { respondError( response, XMPPError.Condition.bad_request, AdHocCommand.SpecificErrorCondition.badAction); return; } // Increase the state number, so the command knows in witch // stage it is command.incrementStage(); // Executes the command command.execute(); if (command.isLastStage()) { // If there is only one stage then the command is completed response.setStatus(Status.completed); } else { // Else it is still executing, and is registered to be // available for the next call response.setStatus(Status.executing); executingCommands.put(sessionId, command); // See if the session reaping thread is started. If not, start it. if (!sessionsSweeper.isAlive()) { sessionsSweeper.start(); } } // Sends the response packet connection.sendPacket(response); } catch (XMPPException e) { // If there is an exception caused by the next, complete, // prev or cancel method, then that error is returned to the // requester. XMPPError error = e.getXMPPError(); // If the error type is cancel, then the execution is // canceled therefore the status must show that, and the // command be removed from the executing list. if (XMPPError.Type.CANCEL.equals(error.getType())) { response.setStatus(Status.canceled); executingCommands.remove(sessionId); } respondError(response, error); e.printStackTrace(); } } else { LocalCommand command = executingCommands.get(sessionId); // Check that a command exists for the specified sessionID // This also handles if the command was removed in the meanwhile // of getting the key and the value of the map. if (command == null) { respondError( response, XMPPError.Condition.bad_request, AdHocCommand.SpecificErrorCondition.badSessionid); return; } // Check if the Session data has expired (default is 10 minutes) long creationStamp = command.getCreationDate(); if (System.currentTimeMillis() - creationStamp > SESSION_TIMEOUT * 1000) { // Remove the expired session executingCommands.remove(sessionId); // Answer a not_allowed error (session-expired) respondError( response, XMPPError.Condition.not_allowed, AdHocCommand.SpecificErrorCondition.sessionExpired); return; } /* * Since the requester could send two requests for the same * executing command i.e. the same session id, all the execution of * the action must be synchronized to avoid inconsistencies. */ synchronized (command) { Action action = requestData.getAction(); // If the action is unknown the respond an error if (action != null && action.equals(Action.unknown)) { respondError( response, XMPPError.Condition.bad_request, AdHocCommand.SpecificErrorCondition.malformedAction); return; } // If the user didn't specify an action or specify the execute // action then follow the actual default execute action if (action == null || Action.execute.equals(action)) { action = command.getExecuteAction(); } // Check that the specified action was previously // offered if (!command.isValidAction(action)) { respondError( response, XMPPError.Condition.bad_request, AdHocCommand.SpecificErrorCondition.badAction); return; } try { // TODO: Check that all the requierd fields of the form are // TODO: filled, if not throw an exception. This will simplify the // TODO: construction of new commands // Since all errors were passed, the response is now a // result response.setType(IQ.Type.RESULT); // Set the new data to the command. command.setData(response); if (Action.next.equals(action)) { command.incrementStage(); command.next(new Form(requestData.getForm())); if (command.isLastStage()) { // If it is the last stage then the command is // completed response.setStatus(Status.completed); } else { // Otherwise it is still executing response.setStatus(Status.executing); } } else if (Action.complete.equals(action)) { command.incrementStage(); command.complete(new Form(requestData.getForm())); response.setStatus(Status.completed); // Remove the completed session executingCommands.remove(sessionId); } else if (Action.prev.equals(action)) { command.decrementStage(); command.prev(); } else if (Action.cancel.equals(action)) { command.cancel(); response.setStatus(Status.canceled); // Remove the canceled session executingCommands.remove(sessionId); } connection.sendPacket(response); } catch (XMPPException e) { // If there is an exception caused by the next, complete, // prev or cancel method, then that error is returned to the // requester. XMPPError error = e.getXMPPError(); // If the error type is cancel, then the execution is // canceled therefore the status must show that, and the // command be removed from the executing list. if (XMPPError.Type.CANCEL.equals(error.getType())) { response.setStatus(Status.canceled); executingCommands.remove(sessionId); } respondError(response, error); e.printStackTrace(); } } } }