/** * $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 java.applet.Applet; import java.applet.AudioClip; import java.awt.Color; import java.awt.Cursor; import java.awt.Font; import java.awt.GridBagConstraints; import java.awt.GridBagLayout; import java.awt.Insets; import java.awt.event.MouseAdapter; import java.awt.event.MouseEvent; import java.text.SimpleDateFormat; import java.util.Date; import java.util.TimerTask; import javax.swing.BorderFactory; import javax.swing.JButton; import javax.swing.JComponent; import javax.swing.JLabel; import javax.swing.JPanel; import javax.swing.SwingUtilities; import org.jivesoftware.resource.Res; import org.jivesoftware.smack.XMPPException; import org.jivesoftware.smackx.jingle.JingleSession; import org.jivesoftware.smackx.jingle.JingleSessionStatePending; 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.component.FileDragLabel; import org.jivesoftware.spark.phone.PhoneManager; import org.jivesoftware.spark.ui.ChatRoom; import org.jivesoftware.spark.ui.ChatRoomClosingListener; import org.jivesoftware.spark.ui.ContactItem; import org.jivesoftware.spark.ui.ContactList; import org.jivesoftware.spark.util.SwingTimerTask; import org.jivesoftware.spark.util.TaskEngine; import org.jivesoftware.spark.util.log.Log; /** * Handles UI controls for outgoing jingle calls. */ public class OutgoingCall extends JPanel implements JingleSessionListener, ChatRoomClosingListener { private static final long serialVersionUID = 7051515951813136423L; private FileDragLabel imageLabel = new FileDragLabel(); private JLabel titleLabel = new JLabel(); private JLabel fileLabel = new JLabel(); private CallButton cancelButton = new CallButton(); private JingleSession session; private JingleRoom jingleRoom; private AudioClip ringing; private ChatRoom chatRoom; private boolean established = false; private boolean mediaReceived = false; private TimerTask mediaReceivedTask; private static final long WAIT_FOR_MEDIA_DELAY = 20000; /** * Creates a new instance of OutgoingCall. */ public OutgoingCall() { try { ringing = Applet.newAudioClip(JinglePhoneRes.getURL("RINGING")); } catch (Exception e) { Log.error(e); } setLayout(new GridBagLayout()); setBackground(new Color(250, 249, 242)); add(imageLabel, new GridBagConstraints(0, 0, 1, 3, 0.0, 0.0, GridBagConstraints.NORTHWEST, GridBagConstraints.NONE, new Insets(5, 5, 5, 5), 0, 0)); add(titleLabel, new GridBagConstraints(1, 0, 2, 1, 1.0, 0.0, GridBagConstraints.NORTHWEST, GridBagConstraints.NONE, new Insets(5, 5, 5, 5), 0, 0)); titleLabel.setFont(new Font("Dialog", Font.BOLD, 11)); titleLabel.setForeground(new Color(211, 174, 102)); cancelButton.setText(Res.getString("cancel")); add(cancelButton, new GridBagConstraints(2, 3, 1, 1, 0.0, 0.0, GridBagConstraints.WEST, GridBagConstraints.NONE, new Insets(0, 5, 0, 5), 0, 0)); cancelButton.setForeground(new Color(73, 113, 196)); cancelButton.setFont(new Font("Dialog", Font.BOLD, 11)); cancelButton.setBorder(BorderFactory.createMatteBorder(0, 0, 1, 0, new Color(73, 113, 196))); setBorder(BorderFactory.createMatteBorder(0, 0, 1, 0, Color.white)); } /** * Handles a new outgoing call. * * @param session the JingleSession for the outgoing call. * @param chatRoom the room the session is associated with. * @param jid the users jid. */ public void handleOutgoingCall(final JingleSession session, ChatRoom chatRoom, final String jid) { this.chatRoom = chatRoom; JingleStateManager.getInstance().addJingleSession(chatRoom, JingleStateManager.JingleRoomState.ringing); chatRoom.addClosingListener(this); session.addListener(this); cancelButton.setVisible(true); this.session = session; // Start the call this.session.startOutgoing(); fileLabel.setText(jid); ContactList contactList = SparkManager.getWorkspace().getContactList(); ContactItem contactItem = contactList.getContactItemByJID(jid); titleLabel.setText(JingleResources.getString("label.outgoing.voicechat", contactItem.getNickname())); cancelButton.addMouseListener(new MouseAdapter() { public void mousePressed(MouseEvent e) { cancel(); } public void mouseEntered(MouseEvent e) { cancelButton.setCursor(new Cursor(Cursor.HAND_CURSOR)); } public void mouseExited(MouseEvent e) { cancelButton.setCursor(new Cursor(Cursor.DEFAULT_CURSOR)); } }); makeClickable(imageLabel); makeClickable(titleLabel); // Notify state change SparkManager.getChatManager().notifySparkTabHandlers(chatRoom); updateOutgoingCallPanel(); } /** * Updates the UI to reflect the current state. */ private void updateOutgoingCallPanel() { if (session == null || session.isClosed()) { return; } else if (session instanceof JingleSession) { showAlert(false); if (session.getSessionState() instanceof JingleSessionStatePending) { titleLabel.setText("Calling user. Please wait..."); cancelButton.setVisible(true); } } } /** * Called when the call has been answered. Will append the JingleRoom to the * associated ChatRoom. */ private void showCallAnsweredState() { final SimpleDateFormat formatter = new SimpleDateFormat("MM/dd/yyyy h:mm a"); titleLabel.setText("Voice chat started on " + formatter.format(new Date())); cancelButton.setVisible(false); if (ringing != null) { ringing.stop(); } jingleRoom = new JingleRoom(session, chatRoom); chatRoom.getChatPanel().add(jingleRoom, 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(); // Add state JingleStateManager.getInstance().addJingleSession(chatRoom, JingleStateManager.JingleRoomState.inJingleCall); // Notify state change SparkManager.getChatManager().notifySparkTabHandlers(chatRoom); } /** * Called when the call has ended. */ private void showCallEndedState(String reason) { titleLabel.setText(reason); showAlert(true); cancelButton.setVisible(false); if (ringing != null) { ringing.stop(); } if (chatRoom != null && jingleRoom != null) { chatRoom.getChatPanel().remove(jingleRoom); chatRoom.getChatPanel().invalidate(); chatRoom.getChatPanel().validate(); chatRoom.getChatPanel().repaint(); } // Add state JingleStateManager.getInstance().removeJingleSession(chatRoom); // Notify state change SparkManager.getChatManager().notifySparkTabHandlers(chatRoom); } private void makeClickable(final JComponent component) { component.addMouseListener(new MouseAdapter() { public void mouseClicked(MouseEvent e) { } public void mouseEntered(MouseEvent e) { component.setCursor(new Cursor(Cursor.HAND_CURSOR)); } public void mouseExited(MouseEvent e) { component.setCursor(new Cursor(Cursor.DEFAULT_CURSOR)); } }); } private class CallButton extends JButton { private static final long serialVersionUID = 7083309769944609925L; public CallButton() { decorate(); } /** * Decorates the button with the approriate UI configurations. */ private void decorate() { setBorderPainted(false); setOpaque(true); setContentAreaFilled(false); setMargin(new Insets(1, 1, 1, 1)); } } /** * Changes the background color. If alert is true, the background will reflect that the ui * needs attention. * * @param alert true to notify users that their attention is needed. */ private void showAlert(boolean alert) { if (alert) { titleLabel.setForeground(new Color(211, 174, 102)); setBackground(new Color(250, 249, 242)); } else { setBackground(new Color(239, 245, 250)); titleLabel.setForeground(new Color(65, 139, 179)); } } /** * Call to cancel phone conversation. */ public void cancel() { if (session != null) { try { session.terminate(); session = null; } catch (XMPPException e) { Log.error(e); } } final SimpleDateFormat formatter = new SimpleDateFormat("MM/dd/yyyy h:mm a"); showCallEndedState("Voice chat ended on " + formatter.format(new Date())); } public void closing() { if (session != null) { try { session.terminate(); } catch (XMPPException e) { Log.error(e); } } JingleStateManager.getInstance().removeJingleSession(chatRoom); } 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); SwingUtilities.invokeLater(new Runnable() { public void run() { updateOutgoingCallPanel(); } }); } public void sessionDeclined(String string, JingleSession jingleSession) { showCallEndedState("The Session was rejected."); SwingUtilities.invokeLater(new Runnable() { public void run() { updateOutgoingCallPanel(); } }); } public void sessionRedirected(String string, JingleSession jingleSession) { SwingUtilities.invokeLater(new Runnable() { public void run() { updateOutgoingCallPanel(); } }); } public void sessionClosed(String string, JingleSession jingleSession) { if (jingleSession instanceof 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("Session closed due to " + string); } } if(PhoneManager.isUseStaticLocator()&&PhoneManager.isUsingMediaLocator()){ PhoneManager.setUsingMediaLocator(false); } SwingUtilities.invokeLater(new Runnable() { public void run() { updateOutgoingCallPanel(); } }); } public void sessionClosedOnError(XMPPException xmppException, JingleSession jingleSession) { showCallEndedState("Voice chat ended due: " + xmppException.getMessage()); if(PhoneManager.isUseStaticLocator()&&PhoneManager.isUsingMediaLocator()){ PhoneManager.setUsingMediaLocator(false); } SwingUtilities.invokeLater(new Runnable() { public void run() { updateOutgoingCallPanel(); } }); } }