/** * $RCSfile: ,v $ * $Revision: $ * $Date: $ * * Copyright (C) 2004-2011 Jive Software. All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.jivesoftware.sparkplugin; import org.jivesoftware.resource.SparkRes; import org.jivesoftware.smack.XMPPException; import org.jivesoftware.smack.util.StringUtils; import org.jivesoftware.smackx.jingle.JingleSession; import org.jivesoftware.smackx.jingle.JingleSessionRequest; import org.jivesoftware.smackx.jingle.listeners.JingleSessionListener; import org.jivesoftware.smackx.jingle.media.PayloadType; import org.jivesoftware.smackx.jingle.nat.TransportCandidate; import org.jivesoftware.spark.SparkManager; import org.jivesoftware.spark.phone.PhoneManager; import org.jivesoftware.spark.ui.ChatRoom; import org.jivesoftware.spark.ui.ChatRoomClosingListener; import org.jivesoftware.spark.util.SwingTimerTask; import org.jivesoftware.spark.util.TaskEngine; import org.jivesoftware.spark.util.log.Log; import org.jivesoftware.sparkimpl.plugin.alerts.SparkToaster; import javax.swing.*; import java.applet.Applet; import java.applet.AudioClip; import java.awt.*; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.text.SimpleDateFormat; import java.util.Date; import java.util.HashMap; import java.util.Map; import java.util.TimerTask; /** * Incoming call handles a single incoming Jingle call. */ public class IncomingCall implements JingleSessionListener, ChatRoomClosingListener { private SparkToaster toasterManager; private AudioClip ringing; private ChatRoom chatRoom; private Map<ChatRoom, JingleRoom> callMap = new HashMap<ChatRoom, JingleRoom>(); private GenericNotification notificationUI; private JingleSession session; private boolean established = false; private boolean mediaReceived = false; private TimerTask mediaReceivedTask; private static final long WAIT_FOR_MEDIA_DELAY = 20000; /** * Initializes a new IncomingCall with the required JingleSession. * * @param request the <code>JingleSessionRequest</code> */ public IncomingCall(final JingleSessionRequest request) { try { ringing = Applet.newAudioClip(JinglePhoneRes.getURL("RINGING")); } catch (Exception e) { Log.error(e); } notificationUI = new GenericNotification(JingleResources.getString("label.establishing.call"), SparkRes.getImageIcon(SparkRes.BUSY_IMAGE)); // Accept the request try { session = request.accept(); } catch (XMPPException e) { Log.error(e); } session.addListener(this); showIncomingCall(request); } /** * Appends the JingleRoom to the ChatRoom. */ private void showCallAnsweredState() { SwingUtilities.invokeLater(new Runnable() { public void run() { final SimpleDateFormat formatter = new SimpleDateFormat("MM/dd/yyyy h:mm a"); notificationUI.setTitle("Voice chat started on " + formatter.format(new Date())); notificationUI.showAlert(false); notificationUI.setIcon(null); if (ringing != null) { ringing.stop(); } final JingleRoom roomUI = new JingleRoom(session, chatRoom); chatRoom.getChatPanel().add(roomUI, new GridBagConstraints(1, 1, 1, 1, 0.05, 1.0, GridBagConstraints.CENTER, GridBagConstraints.BOTH, new Insets(5, 5, 5, 5), 0, 0)); chatRoom.getChatPanel().invalidate(); chatRoom.getChatPanel().validate(); chatRoom.getChatPanel().repaint(); callMap.put(chatRoom, roomUI); // Add state JingleStateManager.getInstance().addJingleSession(chatRoom, JingleStateManager.JingleRoomState.inJingleCall); // Notify state change SparkManager.getChatManager().notifySparkTabHandlers(chatRoom); } }); } /** * Removes the JingleRoom from the ChatRoom. */ private void showCallEndedState(final String reason) { SwingUtilities.invokeLater(new Runnable() { public void run() { if (ringing != null) { ringing.stop(); } notificationUI.setTitle(reason); notificationUI.setIcon(null); notificationUI.showAlert(false); if (chatRoom != null) { JingleRoom room = callMap.get(chatRoom); if (room != null) { chatRoom.getChatPanel().remove(room); } callMap.remove(chatRoom); chatRoom.getChatPanel().invalidate(); chatRoom.getChatPanel().validate(); chatRoom.getChatPanel().repaint(); } // Add state JingleStateManager.getInstance().removeJingleSession(chatRoom); // Notify state change SparkManager.getChatManager().notifySparkTabHandlers(chatRoom); } }); } /** * Called if an incoming Jingle Session is rejected. */ public void rejectIncomingCall() { // Close toaster if it's up. if (toasterManager != null) { toasterManager.close(); } if (session != null) { try { session.terminate(); session = null; } catch (XMPPException e) { Log.error(e); } } if (ringing != null) { ringing.stop(); } } /** * Returns the <code>JingleSession</code> associated with this incoming call. * * @return the session. */ public JingleSession getSession() { return session; } private void notifyRoom() { notificationUI.showAlert(true); chatRoom.getTranscriptWindow().addComponent(notificationUI); } /** * Notifies user of an incoming call. The UI allows for users to either accept or reject * the incoming session. * * @param request the JingleSession. */ private void showIncomingCall(final JingleSessionRequest request) { toasterManager = new SparkToaster(); toasterManager.setHidable(false); final IncomingCallUI incomingCall = new IncomingCallUI(request.getFrom()); toasterManager.setToasterHeight(175); toasterManager.setToasterWidth(300); toasterManager.setDisplayTime(500000000); toasterManager.showToaster("Incoming Voice Chat", incomingCall); toasterManager.hideTitle(); incomingCall.getAcceptButton().addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { acceptSession(request); } }); incomingCall.getRejectButton().addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { rejectIncomingCall(); } }); // Start the ringing. final Runnable ringer = new Runnable() { public void run() { ringing.loop(); } }; TaskEngine.getInstance().submit(ringer); // End after 30 seconds max. TimerTask endTask = new SwingTimerTask() { public void doRun() { if (!session.isFullyEstablished()) { rejectIncomingCall(); } } }; TaskEngine.getInstance().schedule(endTask, 30000); } /** * Accepts a <code>JingleSessionRequest</code>. * * @param request the request. */ private void acceptSession(JingleSessionRequest request) { toasterManager.close(); if (ringing != null) { ringing.stop(); } // Start the call session.startIncoming(); if (chatRoom == null) { chatRoom = SparkManager.getChatManager().getChatRoom(StringUtils.parseBareAddress(request.getFrom())); SparkManager.getChatManager().getChatContainer().activateChatRoom(chatRoom); SparkManager.getChatManager().getChatContainer().getChatFrame().toFront(); notifyRoom(); } } public void sessionMediaReceived(JingleSession jingleSession, String participant) { mediaReceived = true; TaskEngine.getInstance().cancelScheduledTask(mediaReceivedTask); showCallAnsweredState(); } public void sessionEstablished(PayloadType payloadType, TransportCandidate transportCandidate, TransportCandidate transportCandidate1, JingleSession jingleSession) { established = true; mediaReceivedTask = new SwingTimerTask() { public void doRun() { if (!mediaReceived) { if (session != null) { try { session.terminate("No Media Received. This may be caused by firewall configuration problems."); } catch (XMPPException e) { Log.error(e); } } } } }; TaskEngine.getInstance().schedule(mediaReceivedTask, WAIT_FOR_MEDIA_DELAY, WAIT_FOR_MEDIA_DELAY); } public void sessionDeclined(String string, JingleSession jingleSession) { showCallEndedState("Voice chat was rejected"); } public void sessionRedirected(String string, JingleSession jingleSession) { } public void sessionClosed(String string, JingleSession jingleSession) { if (established && mediaReceived) { final SimpleDateFormat formatter = new SimpleDateFormat("MM/dd/yyyy h:mm a"); showCallEndedState("Voice chat ended on " + formatter.format(new Date())); } else { showCallEndedState("Voice chat ended: " + string); } if(PhoneManager.isUseStaticLocator()&&PhoneManager.isUsingMediaLocator()){ PhoneManager.setUsingMediaLocator(false); } } public void sessionClosedOnError(XMPPException xmppException, JingleSession jingleSession) { showCallEndedState("Voice chat ended due an error: " + xmppException.getMessage()); if(PhoneManager.isUseStaticLocator()&&PhoneManager.isUsingMediaLocator()){ PhoneManager.setUsingMediaLocator(false); } } public void closing() { if (session != null) { try { session.terminate(); } catch (XMPPException e) { Log.error(e); } } JingleStateManager.getInstance().removeJingleSession(chatRoom); } }