public void actionPerformed(ActionEvent e) { try { undo.redo(); } catch (CannotRedoException ex) { Logger.getLogger(RedoAction.class.getName()).log(Level.SEVERE, "Unable to redo", ex); } update(); undoAction.update(); }
@Override @SuppressWarnings("SleepWhileHoldingLock") public void run() { try { // initialize the statusbar status.removeAll(); JProgressBar progress = new JProgressBar(); progress.setMinimum(0); progress.setMaximum(doc.getLength()); status.add(progress); status.revalidate(); // start writing Writer out = new FileWriter(f); Segment text = new Segment(); text.setPartialReturn(true); int charsLeft = doc.getLength(); int offset = 0; while (charsLeft > 0) { doc.getText(offset, Math.min(4096, charsLeft), text); out.write(text.array, text.offset, text.count); charsLeft -= text.count; offset += text.count; progress.setValue(offset); try { Thread.sleep(10); } catch (InterruptedException e) { Logger.getLogger(FileSaver.class.getName()).log(Level.SEVERE, null, e); } } out.flush(); out.close(); } catch (IOException e) { final String msg = e.getMessage(); SwingUtilities.invokeLater( new Runnable() { public void run() { JOptionPane.showMessageDialog( getFrame(), "Could not save file: " + msg, "Error saving file", JOptionPane.ERROR_MESSAGE); } }); } catch (BadLocationException e) { System.err.println(e.getMessage()); } // we are done... get rid of progressbar status.removeAll(); status.revalidate(); }
/** * Dialog box to get configuration options for client application. This class provides a standard * dialog box which allows the user to select the location of the database (which may be a physical * file in local mode, or the address (and, optionally, the port) of the server. The user can, of * course, cancel, in which case the application should not start (this is at the applications * discretion though - the business logic could be changed later in the calling class to decide to * start the application anyway if there configuration info can be loaded from file).<br> */ public class DatabaseLocationDialog extends WindowAdapter implements ActionListener, Observer { /* * The strings for the title and buttons in the dialog box. While these * could be hard coded in the application, having them here makes it * easier to use internationalization options such as a ResourceBundle. */ private static final String TITLE = "Please enter database location"; private static final String CONNECT = "Connect"; private static final String EXIT = "Exit"; /* * Some values for possible port ranges so we can determine what sort of * port the user has specified. */ private static final int LOWEST_PORT = 0; private static final int HIGHEST_PORT = 65535; private static final int SYSTEM_PORT_BOUNDARY = 1024; private static final int IANA_PORT_BOUNDARY = 49151; /* * The bits and pieces that comprise our dialog box. They are all global so * we can disable them or enable them as the user enters valid information. */ private JOptionPane options = null; private JDialog dialog = null; private JButton connectButton = new JButton(CONNECT); private JButton exitButton = new JButton(EXIT); /* * The common panel that is used by both the client and the server for * specifying where the database is. */ private ConfigOptions configOptions = null; /* * Flags to show whether enough information has been provided for us to * start the application. */ private boolean validDb = false; private boolean validPort = false; private boolean validCnx = false; /* * Details specified in the configOptions pane detailing where the database * is. */ private String location = null; private String port = null; private ConnectionType networkType = null; /* * The Logger instance. All log messages from this class are routed through * this member. The Logger namespace is <code>sampleproject.gui</code>. */ private Logger log = Logger.getLogger("sampleproject.gui"); /** * Creates a dialog where the user can specify the location of the database,including the type of * network connection (if this is a networked client)and IP address and port number; or search and * select the database on a local drive if this is a standalone client. * * @param parent Defines the Component that is to be the parent of this dialog box. For * information on how this is used, see <code>JOptionPane</code> * @param connectionMode Specifies the type of connection (standalone or networked) * @see JOptionPane */ public DatabaseLocationDialog(Frame parent, ApplicationMode connectionMode) { configOptions = (new ConfigOptions(connectionMode)); configOptions.getObservable().addObserver(this); // load saved configuration SavedConfiguration config = SavedConfiguration.getSavedConfiguration(); // the port and connection type are irrelevant in standalone mode if (connectionMode == ApplicationMode.STANDALONE_CLIENT) { validPort = true; validCnx = true; networkType = ConnectionType.DIRECT; location = config.getParameter(SavedConfiguration.DATABASE_LOCATION); } else { // there may not be a network connectivity type defined and, if // not, we do not set a default - force the user to make a choice // the at least for the first time they run this. String tmp = config.getParameter(SavedConfiguration.NETWORK_TYPE); if (tmp != null) { try { networkType = ConnectionType.valueOf(tmp); configOptions.setNetworkConnection(networkType); validCnx = true; } catch (IllegalArgumentException e) { log.warning("Unknown connection type: " + networkType); } } // there is always at least a default port number, so we don't have // to validate this. port = config.getParameter(SavedConfiguration.SERVER_PORT); configOptions.setPortNumberText(port); validPort = true; location = config.getParameter(SavedConfiguration.SERVER_ADDRESS); } // there may not be a default database location, so we had better // validate before using the returned value. if (location != null) { configOptions.setLocationFieldText(location); validDb = true; } options = new JOptionPane(configOptions, JOptionPane.QUESTION_MESSAGE, JOptionPane.OK_CANCEL_OPTION); connectButton.setActionCommand(CONNECT); connectButton.addActionListener(this); boolean allValid = validDb && validPort && validCnx; connectButton.setEnabled(allValid); exitButton.setActionCommand(EXIT); exitButton.addActionListener(this); options.setOptions(new Object[] {connectButton, exitButton}); dialog = options.createDialog(parent, TITLE); dialog.setDefaultCloseOperation(JDialog.DO_NOTHING_ON_CLOSE); dialog.addWindowListener(this); dialog.setVisible(true); } // Note: we can get away with not specifying the parameters in these // callback methods, as they are specified in the interfaces we are // implementing. /** * Callback event handler to process situations where the user has closed the window rather than * clicking one of the buttons. */ public void windowClosing(WindowEvent we) { processCommand(EXIT); } /** Callback event handler to process clicks on any of the buttons. */ public void actionPerformed(ActionEvent ae) { processCommand(ae.getActionCommand()); } /** * Common event handling code - can handle desirable actions (such as buttons being clicked) and * undesirable actions (the window being closed) all in a common location. * * @param command a String representing the action that occurred. */ private void processCommand(String command) { dialog.setVisible(false); if (CONNECT.equals(command)) { options.setValue(JOptionPane.OK_OPTION); } else { options.setValue(JOptionPane.CANCEL_OPTION); } } /** * Callback method to process modifications in the common ConfigOptions panel. ConfigOptions was * developed to be common to many applications (even though we only have three modes), so it does * not have any knowledge of how we are using it within this dialog box. So ConfigOptions just * sends updates to registered Observers whenever anything changes. We can receive those * notifications here, and decide whether we have enough information to enable the "Connect" * button of the dialog box. */ public void update(Observable o, Object arg) { // we are going to ignore the Observable object, since we are only // observing one object. All we are interested in is the argument. if (!(arg instanceof OptionUpdate)) { log.log( Level.WARNING, "DatabaseLocationDialog received update type: " + arg, new IllegalArgumentException()); return; } OptionUpdate optionUpdate = (OptionUpdate) arg; // load saved configuration SavedConfiguration config = SavedConfiguration.getSavedConfiguration(); switch (optionUpdate.getUpdateType()) { case DB_LOCATION_CHANGED: location = (String) optionUpdate.getPayload(); if (configOptions.getApplicationMode() == ApplicationMode.STANDALONE_CLIENT) { File f = new File(location); if (f.exists() && f.canRead() && f.canWrite()) { validDb = true; log.info("File chosen " + location); config.setParameter(SavedConfiguration.DATABASE_LOCATION, location); } else { log.warning("Invalid file " + location); } } else { try { if (location.matches("\\d+\\.\\d+\\.\\d+\\.\\d+")) { // location given matches 4 '.' separated numbers // regex could be improved by limiting each quad to // no more than 3 digits. String[] quads = location.split("\\."); byte[] address = new byte[quads.length]; for (int i = 0; i < quads.length; i++) { address[i] = new Integer(quads[i]).byteValue(); } InetAddress.getByAddress(address); } else { InetAddress.getAllByName(location); } log.info("Server specified " + location); validDb = true; config.setParameter(SavedConfiguration.SERVER_ADDRESS, location); } catch (UnknownHostException uhe) { log.warning("Unknown host: " + location); validDb = false; } } break; case PORT_CHANGED: port = (String) optionUpdate.getPayload(); int p = Integer.parseInt(port); if (p >= LOWEST_PORT && p < HIGHEST_PORT) { if (p < SYSTEM_PORT_BOUNDARY) { log.info("User chose System port " + port); } else if (p < IANA_PORT_BOUNDARY) { log.info("User chose IANA port " + port); } else { log.info("User chose dynamic port " + port); } validPort = true; config.setParameter(SavedConfiguration.SERVER_PORT, port); } else { validPort = false; } break; case NETWORK_CHOICE_MADE: networkType = (ConnectionType) optionUpdate.getPayload(); switch (networkType) { case SOCKET: log.info("Server connection via Sockets"); break; case RMI: log.info("Server connection via RMI"); break; default: log.info("Unknown connection type: " + networkType); break; } config.setParameter(SavedConfiguration.NETWORK_TYPE, "" + networkType); validCnx = true; break; default: log.warning("Unknown update: " + optionUpdate); break; } boolean allValid = validDb && validPort && validCnx; connectButton.setEnabled(allValid); } /** * Returns the location of the database, which may be either the path to the local database, or * the address of the network server hosting the database. * * @return the location of the database. */ public String getLocation() { return location; } /** * Returns the port number the network server should be listening on for client connections. * * @return the port number for connecting to the network server. */ public String getPort() { return port; } /** * Returns the type of network connection (Sockets, RMI ...) that should be used to connect to the * server. * * @return the network protocol used to connect to the server. */ public ConnectionType getNetworkType() { return networkType; } /** * Let the caller of this dialog know whether the user connected or cancelled. * * @return true if the user cancelled or closed the window. */ public boolean userCanceled() { if (options.getValue() instanceof Integer) { int status = ((Integer) options.getValue()).intValue(); return status != JOptionPane.OK_OPTION; } else { return false; } } }
/** * The <tt>OneToOneCallPeerPanel</tt> is the panel containing data for a call peer in a given call. * It contains information like call peer name, photo, call duration, etc. * * @author Yana Stamcheva * @author Lyubomir Marinov * @author Sebastien Vincent * @author Adam Netocny */ public class OneToOneCallPeerPanel extends TransparentPanel implements SwingCallPeerRenderer, PropertyChangeListener, Skinnable { /** * The <tt>Logger</tt> used by the <tt>OneToOneCallPeerPanel</tt> class and its instances for * logging output. */ private static final Logger logger = Logger.getLogger(OneToOneCallPeerPanel.class); /** Serial version UID. */ private static final long serialVersionUID = 0L; /** The <tt>CallPeer</tt>, which is rendered in this panel. */ private final CallPeer callPeer; /** The <tt>Call</tt>, which is rendered in this panel. */ private final Call call; /** * The <tt>CallPeerAdapter</tt> which implements common <tt>CallPeer</tt>-related listeners on * behalf of this instance. */ private final CallPeerAdapter callPeerAdapter; /** The renderer of the call. */ private final SwingCallRenderer callRenderer; /** The component showing the status of the underlying call peer. */ private final JLabel callStatusLabel = new JLabel(); /** The center component. */ private final VideoContainer center; /** * The AWT <tt>Component</tt> which implements a button which allows closing/hiding the visual * <tt>Component</tt> which depicts the video streaming from the local peer/user to the remote * peer(s). */ private Component closeLocalVisualComponentButton; /** * A listener to desktop sharing granted/revoked events and to mouse and keyboard interaction with * the remote video displaying the remote desktop. */ private final DesktopSharingMouseAndKeyboardListener desktopSharingMouseAndKeyboardListener; /** * The indicator which determines whether {@link #dispose()} has already been invoked on this * instance. If <tt>true</tt>, this instance is considered non-functional and is to be left to the * garbage collector. */ private boolean disposed = false; /** The DTMF label. */ private final JLabel dtmfLabel = new JLabel(); /** The component responsible for displaying an error message. */ private JTextComponent errorMessageComponent; /** The label showing whether the call is on or off hold. */ private final JLabel holdStatusLabel = new JLabel(); /** Sound local level label. */ private InputVolumeControlButton localLevel; /** * The <tt>Component</tt> which {@link #updateViewFromModelInEventDispatchThread()} last added to * {@link #center} as the visual <tt>Component</tt> displaying the video streaming from the local * peer/user to the remote peer(s). * * <p><b>Warning</b>: It is not to be used for any other purposes because it may not represent the * current state of the model of this view. */ private Component localVideo; /** The label showing whether the voice has been set to mute. */ private final JLabel muteStatusLabel = new JLabel(); /** The <tt>Icon</tt> which represents the avatar of the associated call peer. */ private ImageIcon peerImage; /** The name of the peer. */ private String peerName; /** The label containing the user photo. */ private final JLabel photoLabel; /** Sound remote level label. */ private Component remoteLevel; /** * The <tt>Component</tt> which {@link #updateViewFromModelInEventDispatchThread()} last added to * {@link #center} as the visual <tt>Component</tt> displaying the video streaming from the remote * peer(s) to the local peer/user. * * <p><b>Warning</b>: It is not to be used for any other purposes because it may not represent the * current state of the model of this view. */ private Component remoteVideo; /** Current id for security image. */ private ImageID securityImageID = ImageLoader.SECURE_BUTTON_OFF; /** The panel containing security related components. */ private SecurityPanel<?> securityPanel; /** The security status of the peer */ private final SecurityStatusLabel securityStatusLabel = new SecurityStatusLabel(); /** The status bar component. */ private final Component statusBar; /** The facility which aids this instance in the dealing with the video-related information. */ private final UIVideoHandler2 uiVideoHandler; /** * The <tt>Observer</tt> which listens to changes in the video-related information detected and * reported by {@link #uiVideoHandler}. */ private final Observer uiVideoHandlerObserver = new Observer() { public void update(Observable o, Object arg) { updateViewFromModel(); } }; /** * The <tt>Runnable</tt> which is scheduled by {@link #updateViewFromModel()} for execution in the * AWT event dispatching thread in order to invoke {@link * #updateViewFromModelInEventDispatchThread()}. */ private final Runnable updateViewFromModelInEventDispatchThread = new Runnable() { public void run() { /* * We receive events/notifications from various threads and we * respond to them in the AWT event dispatching thread. It is * possible to first schedule an event to be brought to the AWT * event dispatching thread, then to have #dispose() invoked on * this instance and, finally, to receive the scheduled event in * the AWT event dispatching thread. In such a case, this * disposed instance should not respond to the event. */ if (!disposed) updateViewFromModelInEventDispatchThread(); } }; /** * Creates a <tt>CallPeerPanel</tt> for the given call peer. * * @param callRenderer the renderer of the call * @param callPeer the <tt>CallPeer</tt> represented in this panel * @param uiVideoHandler the facility which is to aid the new instance in the dealing with the * video-related information */ public OneToOneCallPeerPanel( SwingCallRenderer callRenderer, CallPeer callPeer, UIVideoHandler2 uiVideoHandler) { this.callRenderer = callRenderer; this.callPeer = callPeer; // we need to obtain call as soon as possible // cause if it fails too quickly we may fail to show it this.call = callPeer.getCall(); this.uiVideoHandler = uiVideoHandler; peerName = CallManager.getPeerDisplayName(callPeer); securityPanel = SecurityPanel.create(this, callPeer, null); photoLabel = new JLabel(getPhotoLabelIcon()); center = createCenter(); statusBar = createStatusBar(); setPeerImage(CallManager.getPeerImage(callPeer)); /* Lay out the main Components of the UI. */ setLayout(new GridBagLayout()); GridBagConstraints cnstrnts = new GridBagConstraints(); if (center != null) { cnstrnts.fill = GridBagConstraints.BOTH; cnstrnts.gridx = 0; cnstrnts.gridy = 1; cnstrnts.weightx = 1; cnstrnts.weighty = 1; add(center, cnstrnts); } if (statusBar != null) { cnstrnts.fill = GridBagConstraints.NONE; cnstrnts.gridx = 0; cnstrnts.gridy = 3; cnstrnts.weightx = 0; cnstrnts.weighty = 0; cnstrnts.insets = new Insets(5, 0, 0, 0); add(statusBar, cnstrnts); } createSoundLevelIndicators(); initSecuritySettings(); /* * Add the listeners which will be notified about changes in the model * and which will update this view. */ callPeerAdapter = new CallPeerAdapter(callPeer, this); uiVideoHandler.addObserver(uiVideoHandlerObserver); /* * This view adapts to whether it is displayed in full-screen or * windowed mode. */ if (callRenderer instanceof Component) { ((Component) callRenderer).addPropertyChangeListener(CallContainer.PROP_FULL_SCREEN, this); } OperationSetDesktopSharingClient desktopSharingClient = callPeer.getProtocolProvider().getOperationSet(OperationSetDesktopSharingClient.class); if (desktopSharingClient != null) { desktopSharingMouseAndKeyboardListener = new DesktopSharingMouseAndKeyboardListener(callPeer, desktopSharingClient); } else desktopSharingMouseAndKeyboardListener = null; updateViewFromModel(); } /** * Creates the <tt>Component</tt> hierarchy of the central area of this <tt>CallPeerPanel</tt> * which displays the photo of the <tt>CallPeer</tt> or the video if any. */ private VideoContainer createCenter() { photoLabel.setPreferredSize(new Dimension(90, 90)); return createVideoContainer(photoLabel); } /** Creates sound level related components. */ private void createSoundLevelIndicators() { TransparentPanel localLevelPanel = new TransparentPanel(new BorderLayout(5, 0)); TransparentPanel remoteLevelPanel = new TransparentPanel(new BorderLayout(5, 0)); localLevel = new InputVolumeControlButton( call, ImageLoader.MICROPHONE, ImageLoader.MUTE_BUTTON, false, false); remoteLevel = new OutputVolumeControlButton(call.getConference(), ImageLoader.HEADPHONE, false, false) .getComponent(); final SoundLevelIndicator localLevelIndicator = new SoundLevelIndicator(SoundLevelChangeEvent.MIN_LEVEL, SoundLevelChangeEvent.MAX_LEVEL); final SoundLevelIndicator remoteLevelIndicator = new SoundLevelIndicator(SoundLevelChangeEvent.MIN_LEVEL, SoundLevelChangeEvent.MAX_LEVEL); localLevelPanel.add(localLevel, BorderLayout.WEST); localLevelPanel.add(localLevelIndicator, BorderLayout.CENTER); remoteLevelPanel.add(remoteLevel, BorderLayout.WEST); remoteLevelPanel.add(remoteLevelIndicator, BorderLayout.CENTER); GridBagConstraints constraints = new GridBagConstraints(); constraints.fill = GridBagConstraints.NONE; constraints.gridx = 0; constraints.gridy = 5; constraints.weightx = 0; constraints.weighty = 0; constraints.insets = new Insets(10, 0, 0, 0); add(localLevelPanel, constraints); constraints.fill = GridBagConstraints.NONE; constraints.gridx = 0; constraints.gridy = 6; constraints.weightx = 0; constraints.weighty = 0; constraints.insets = new Insets(5, 0, 10, 0); add(remoteLevelPanel, constraints); if (!GuiActivator.getConfigurationService() .getBoolean( "net.java.sip.communicator.impl.gui.main.call." + "DISABLE_SOUND_LEVEL_INDICATORS", false)) { callPeer.addStreamSoundLevelListener( new SoundLevelListener() { public void soundLevelChanged(Object source, int level) { remoteLevelIndicator.updateSoundLevel(level); } }); /* * By the time the UI gets to be initialized, the callPeer may have * been removed from its Call. As far as the UI is concerned, the * callPeer will never have a Call again and there will be no audio * levels to display anyway so there is no point in throwing a * NullPointerException here. */ if (call != null) { call.addLocalUserSoundLevelListener( new SoundLevelListener() { public void soundLevelChanged(Object source, int level) { localLevelIndicator.updateSoundLevel(level); } }); } } } /** * Creates the <tt>Component</tt> hierarchy of the area of status-related information such as * <tt>CallPeer</tt> display name, call duration, security status. * * @return the root of the <tt>Component</tt> hierarchy of the area of status-related information * such as <tt>CallPeer</tt> display name, call duration, security status */ private Component createStatusBar() { // stateLabel callStatusLabel.setForeground(Color.WHITE); dtmfLabel.setForeground(Color.WHITE); callStatusLabel.setText(callPeer.getState().getLocalizedStateString()); PeerStatusPanel statusPanel = new PeerStatusPanel(new GridBagLayout()); GridBagConstraints constraints = new GridBagConstraints(); constraints.gridx = 0; constraints.gridy = 0; statusPanel.add(securityStatusLabel, constraints); initSecurityStatusLabel(); constraints.gridx++; statusPanel.add(holdStatusLabel, constraints); constraints.gridx++; statusPanel.add(muteStatusLabel, constraints); constraints.gridx++; callStatusLabel.setBorder(BorderFactory.createEmptyBorder(2, 3, 2, 12)); statusPanel.add(callStatusLabel, constraints); constraints.gridx++; constraints.weightx = 1f; statusPanel.add(dtmfLabel, constraints); return statusPanel; } /** * Creates a new AWT <tt>Container</tt> which can display a single <tt>Component</tt> at a time * (supposedly, one which represents video) and, in the absence of such a <tt>Component</tt>, * displays a predefined default <tt>Component</tt> (in accord with the previous supposition, one * which is the default when there is no video). The returned <tt>Container</tt> will track the * <tt>Components</tt>s added to and removed from it in order to make sure that * <tt>noVideoContainer</tt> is displayed as described. * * @param noVideoComponent the predefined default <tt>Component</tt> to be displayed in the * returned <tt>Container</tt> when there is no other <tt>Component</tt> in it * @return a new <tt>Container</tt> which can display a single <tt>Component</tt> at a time and, * in the absence of such a <tt>Component</tt>, displays <tt>noVideoComponent</tt> */ private VideoContainer createVideoContainer(Component noVideoComponent) { Container oldParent = noVideoComponent.getParent(); if (oldParent != null) oldParent.remove(noVideoComponent); return new VideoContainer(noVideoComponent, false); } /** * Releases the resources acquired by this instance which require explicit disposal (e.g. any * listeners added to the depicted <tt>CallPeer</tt>. Invoked by <tt>OneToOneCallPanel</tt> when * it determines that this <tt>OneToOneCallPeerPanel</tt> is no longer necessary. */ public void dispose() { disposed = true; callPeerAdapter.dispose(); uiVideoHandler.deleteObserver(uiVideoHandlerObserver); if (callRenderer instanceof Component) { ((Component) callRenderer).removePropertyChangeListener(CallContainer.PROP_FULL_SCREEN, this); } } /** * Returns the parent <tt>CallPanel</tt> containing this renderer. * * @return the parent <tt>CallPanel</tt> containing this renderer */ public CallPanel getCallPanel() { return callRenderer.getCallContainer(); } /** * Returns the parent call renderer. * * @return the parent call renderer */ public CallRenderer getCallRenderer() { return callRenderer; } /** * Returns the component associated with this renderer. * * @return the component associated with this renderer */ public Component getComponent() { return this; } /** * Returns the name of the peer, contained in this panel. * * @return the name of the peer, contained in this panel */ public String getPeerName() { return peerName; } /** * Gets the <tt>Icon</tt> to be displayed in {@link #photoLabel}. * * @return the <tt>Icon</tt> to be displayed in {@link #photoLabel} */ private ImageIcon getPhotoLabelIcon() { return (peerImage == null) ? new ImageIcon(ImageLoader.getImage(ImageLoader.DEFAULT_USER_PHOTO)) : peerImage; } /** Initializes the security settings for this call peer. */ private void initSecuritySettings() { CallPeerSecurityStatusEvent securityEvent = callPeer.getCurrentSecuritySettings(); if (securityEvent instanceof CallPeerSecurityOnEvent) securityOn((CallPeerSecurityOnEvent) securityEvent); } /** Initializes the security status label, shown in the call status bar. */ private void initSecurityStatusLabel() { securityStatusLabel.setBorder(BorderFactory.createEmptyBorder(2, 5, 2, 5)); securityStatusLabel.addMouseListener( new MouseAdapter() { /** Invoked when a mouse button has been pressed on a component. */ @Override public void mousePressed(MouseEvent e) { // Only show the security details if the security is on. SrtpControl ctrl = securityPanel.getSecurityControl(); if (ctrl instanceof ZrtpControl && ctrl.getSecureCommunicationStatus()) { setSecurityPanelVisible( !callRenderer .getCallContainer() .getCallWindow() .getFrame() .getGlassPane() .isVisible()); } } }); } /** * Determines whether the visual <tt>Component</tt> depicting the video streaming from the local * peer/user to the remote peer(s) is currently visible. * * @return <tt>true</tt> if the visual <tt>Component</tt> depicting the video streaming from the * local peer/user to the remote peer(s) is currently visible; otherwise, <tt>false</tt> */ public boolean isLocalVideoVisible() { return uiVideoHandler.isLocalVideoVisible(); } /** Reloads all icons. */ public void loadSkin() { if (localLevel != null) localLevel.setIcon(new ImageIcon(ImageLoader.getImage(ImageLoader.MICROPHONE))); if (remoteLevel != null && remoteLevel instanceof Skinnable) ((Skinnable) remoteLevel).loadSkin(); if (muteStatusLabel.getIcon() != null) muteStatusLabel.setIcon(new ImageIcon(ImageLoader.getImage(ImageLoader.MUTE_STATUS_ICON))); if (holdStatusLabel.getIcon() != null) holdStatusLabel.setIcon(new ImageIcon(ImageLoader.getImage(ImageLoader.HOLD_STATUS_ICON))); securityStatusLabel.setIcon(new ImageIcon(ImageLoader.getImage(securityImageID))); if (peerImage == null) { photoLabel.setIcon(new ImageIcon(ImageLoader.getImage(ImageLoader.DEFAULT_USER_PHOTO))); } } /** * Prints the given DTMG character through this <tt>CallPeerRenderer</tt>. * * @param dtmfChar the DTMF char to print */ public void printDTMFTone(char dtmfChar) { dtmfLabel.setText(dtmfLabel.getText() + dtmfChar); if (dtmfLabel.getBorder() == null) dtmfLabel.setBorder(BorderFactory.createEmptyBorder(2, 1, 2, 5)); } /** * Notifies this instance about a change in the value of a property of a source which of interest * to this instance. For example, <tt>OneToOneCallPeerPanel</tt> updates its user * interface-related properties upon changes in the value of the {@link * CallContainer#PROP_FULL_SCREEN} property of its associated {@link #callRenderer}. * * @param ev a <tt>PropertyChangeEvent</tt> which identifies the source, the name of the property * and the old and new values */ public void propertyChange(PropertyChangeEvent ev) { if (CallContainer.PROP_FULL_SCREEN.equals(ev.getPropertyName())) updateViewFromModel(); } /** * Re-dispatches glass pane mouse events only in case they occur on the security panel. * * @param glassPane the glass pane * @param e the mouse event in question */ private void redispatchMouseEvent(Component glassPane, MouseEvent e) { Point glassPanePoint = e.getPoint(); Point securityPanelPoint = SwingUtilities.convertPoint(glassPane, glassPanePoint, securityPanel); Component component; Point componentPoint; if (securityPanelPoint.y > 0) { component = securityPanel; componentPoint = securityPanelPoint; } else { Container contentPane = callRenderer.getCallContainer().getCallWindow().getFrame().getContentPane(); Point containerPoint = SwingUtilities.convertPoint(glassPane, glassPanePoint, contentPane); component = SwingUtilities.getDeepestComponentAt(contentPane, containerPoint.x, containerPoint.y); componentPoint = SwingUtilities.convertPoint(contentPane, glassPanePoint, component); } if (component != null) component.dispatchEvent( new MouseEvent( component, e.getID(), e.getWhen(), e.getModifiers(), componentPoint.x, componentPoint.y, e.getClickCount(), e.isPopupTrigger())); e.consume(); } /** * The handler for the security event received. The security event for starting establish a secure * connection. * * @param evt the security started event received */ public void securityNegotiationStarted(CallPeerSecurityNegotiationStartedEvent evt) { if (Boolean.parseBoolean( GuiActivator.getResources().getSettingsString("impl.gui.PARANOIA_UI"))) { SrtpControl srtpControl = null; if (callPeer instanceof MediaAwareCallPeer) srtpControl = evt.getSecurityController(); securityPanel = new ParanoiaTimerSecurityPanel<SrtpControl>(srtpControl); setSecurityPanelVisible(true); } } /** * Indicates that the security has gone off. * * @param evt the <tt>CallPeerSecurityOffEvent</tt> that notified us */ public void securityOff(final CallPeerSecurityOffEvent evt) { if (!SwingUtilities.isEventDispatchThread()) { SwingUtilities.invokeLater( new Runnable() { public void run() { securityOff(evt); } }); return; } if (evt.getSessionType() == CallPeerSecurityOffEvent.AUDIO_SESSION) { securityStatusLabel.setText(""); securityStatusLabel.setSecurityOff(); if (securityStatusLabel.getBorder() == null) securityStatusLabel.setBorder(BorderFactory.createEmptyBorder(2, 5, 2, 3)); } securityPanel.securityOff(evt); } /** * Indicates that the security is turned on. * * <p>Sets the secured status icon to the status panel and initializes/updates the corresponding * security details. * * @param evt Details about the event that caused this message. */ public void securityOn(final CallPeerSecurityOnEvent evt) { if (!SwingUtilities.isEventDispatchThread()) { SwingUtilities.invokeLater( new Runnable() { public void run() { securityOn(evt); } }); return; } // If the securityOn is called without a specific event, we'll just set // the security label status to on. if (evt == null) { securityStatusLabel.setSecurityOn(); return; } SrtpControl srtpControl = evt.getSecurityController(); if (!srtpControl.requiresSecureSignalingTransport() || callPeer.getProtocolProvider().isSignalingTransportSecure()) { if (srtpControl instanceof ZrtpControl) { securityStatusLabel.setText("zrtp"); if (!((ZrtpControl) srtpControl).isSecurityVerified()) securityStatusLabel.setSecurityPending(); else securityStatusLabel.setSecurityOn(); } else securityStatusLabel.setSecurityOn(); } // if we have some other panel, using other control if (!srtpControl.getClass().isInstance(securityPanel.getSecurityControl()) || (securityPanel instanceof ParanoiaTimerSecurityPanel)) { setSecurityPanelVisible(false); securityPanel = SecurityPanel.create(this, callPeer, srtpControl); if (srtpControl instanceof ZrtpControl) ((ZrtpSecurityPanel) securityPanel).setSecurityStatusLabel(securityStatusLabel); } securityPanel.securityOn(evt); boolean isSecurityLowPriority = Boolean.parseBoolean( GuiActivator.getResources() .getSettingsString("impl.gui.I_DONT_CARE_THAT_MUCH_ABOUT_SECURITY")); // Display ZRTP panel in case SAS was not verified or a AOR mismtach // was detected during creation of ZrtpSecurityPanel. // Don't show panel if user does not care about security at all. if (srtpControl instanceof ZrtpControl && !isSecurityLowPriority && (!((ZrtpControl) srtpControl).isSecurityVerified() || ((ZrtpSecurityPanel) securityPanel).isZidAorMismatch())) { setSecurityPanelVisible(true); } this.revalidate(); } /** Indicates that the security status is pending confirmation. */ public void securityPending() { securityStatusLabel.setSecurityPending(); } /** * Indicates that the security is timeouted, is not supported by the other end. * * @param evt Details about the event that caused this message. */ public void securityTimeout(final CallPeerSecurityTimeoutEvent evt) { if (!SwingUtilities.isEventDispatchThread()) { SwingUtilities.invokeLater( new Runnable() { public void run() { securityTimeout(evt); } }); return; } if (securityPanel != null) securityPanel.securityTimeout(evt); } /** * Sets the reason of a call failure if one occurs. The renderer should display this reason to the * user. * * @param reason the reason to display */ public void setErrorReason(final String reason) { if (!SwingUtilities.isEventDispatchThread()) { SwingUtilities.invokeLater( new Runnable() { public void run() { setErrorReason(reason); } }); return; } if (errorMessageComponent == null) { errorMessageComponent = new JTextPane(); JTextPane textPane = (JTextPane) errorMessageComponent; textPane.setEditable(false); textPane.setOpaque(false); StyledDocument doc = textPane.getStyledDocument(); MutableAttributeSet standard = new SimpleAttributeSet(); StyleConstants.setAlignment(standard, StyleConstants.ALIGN_CENTER); StyleConstants.setFontFamily(standard, textPane.getFont().getFamily()); StyleConstants.setFontSize(standard, 12); doc.setParagraphAttributes(0, 0, standard, true); GridBagConstraints constraints = new GridBagConstraints(); constraints.fill = GridBagConstraints.HORIZONTAL; constraints.gridx = 0; constraints.gridy = 4; constraints.weightx = 1; constraints.weighty = 0; constraints.insets = new Insets(5, 0, 0, 0); add(errorMessageComponent, constraints); this.revalidate(); } errorMessageComponent.setText(reason); if (isVisible()) errorMessageComponent.repaint(); } /** * Shows/hides the visual <tt>Component</tt> depicting the video streaming from the local * peer/user to the remote peer(s). * * @param visible <tt>true</tt> to show the visual <tt>Component</tt> depicting the video * streaming from the local peer/user to the remote peer(s); <tt>false</tt>, otherwise */ public void setLocalVideoVisible(boolean visible) { uiVideoHandler.setLocalVideoVisible(visible); } /** * Sets the mute status icon to the status panel. * * @param isMute indicates if the call with this peer is muted */ public void setMute(final boolean isMute) { if (!SwingUtilities.isEventDispatchThread()) { SwingUtilities.invokeLater( new Runnable() { public void run() { setMute(isMute); } }); return; } if (isMute) { muteStatusLabel.setIcon(new ImageIcon(ImageLoader.getImage(ImageLoader.MUTE_STATUS_ICON))); muteStatusLabel.setBorder(BorderFactory.createEmptyBorder(2, 3, 2, 3)); } else { muteStatusLabel.setIcon(null); muteStatusLabel.setBorder(BorderFactory.createEmptyBorder(0, 0, 0, 0)); } // Update input volume control button state to reflect the current // mute status. if (localLevel.isSelected() != isMute) localLevel.setSelected(isMute); this.revalidate(); this.repaint(); } /** * Sets the "on hold" property value. * * @param isOnHold indicates if the call with this peer is put on hold */ public void setOnHold(final boolean isOnHold) { if (!SwingUtilities.isEventDispatchThread()) { SwingUtilities.invokeLater( new Runnable() { public void run() { setOnHold(isOnHold); } }); return; } if (isOnHold) { holdStatusLabel.setIcon(new ImageIcon(ImageLoader.getImage(ImageLoader.HOLD_STATUS_ICON))); holdStatusLabel.setBorder(BorderFactory.createEmptyBorder(2, 3, 2, 3)); } else { holdStatusLabel.setIcon(null); holdStatusLabel.setBorder(BorderFactory.createEmptyBorder(0, 0, 0, 0)); } this.revalidate(); this.repaint(); } /** * Set the image of the peer * * @param image new image */ public void setPeerImage(byte[] image) { // If the image is still null we try to obtain it from one of the // available contact sources. if (image == null || image.length <= 0) { GuiActivator.getContactList().setSourceContactImage(peerName, photoLabel, 100, 100); } else { peerImage = ImageUtils.getScaledRoundedIcon(image, 100, 100); if (peerImage == null) peerImage = getPhotoLabelIcon(); if (!SwingUtilities.isEventDispatchThread()) { SwingUtilities.invokeLater( new Runnable() { public void run() { photoLabel.setIcon(peerImage); photoLabel.repaint(); } }); } else { photoLabel.setIcon(peerImage); photoLabel.repaint(); } } } /** * Sets the name of the peer. * * @param name the name of the peer */ public void setPeerName(final String name) { if (!SwingUtilities.isEventDispatchThread()) { SwingUtilities.invokeLater( new Runnable() { public void run() { setPeerName(name); } }); return; } peerName = name; ((OneToOneCallPanel) callRenderer).setPeerName(name); } /** * Sets the state of the contained call peer by specifying the state name. * * @param oldState the previous state of the peer * @param newState the new state of the peer * @param stateString the state of the contained call peer */ public void setPeerState( final CallPeerState oldState, final CallPeerState newState, final String stateString) { if (!SwingUtilities.isEventDispatchThread()) { SwingUtilities.invokeLater( new Runnable() { public void run() { setPeerState(oldState, newState, stateString); } }); return; } this.callStatusLabel.setText(stateString); if (newState == CallPeerState.CONNECTED && !CallPeerState.isOnHold(oldState) && !securityStatusLabel.isSecurityStatusSet()) { securityStatusLabel.setSecurityOff(); } } /** * Shows/hides the security panel. * * @param isVisible <tt>true</tt> to show the security panel, <tt>false</tt> to hide it */ public void setSecurityPanelVisible(final boolean isVisible) { if (!SwingUtilities.isEventDispatchThread()) { SwingUtilities.invokeLater( new Runnable() { public void run() { setSecurityPanelVisible(isVisible); } }); return; } final JFrame callFrame = callRenderer.getCallContainer().getCallWindow().getFrame(); final JPanel glassPane = (JPanel) callFrame.getGlassPane(); if (!isVisible) { // Need to hide the security panel explicitly in order to keep the // fade effect. securityPanel.setVisible(false); glassPane.setVisible(false); glassPane.removeAll(); } else { glassPane.setLayout(null); glassPane.addMouseListener( new MouseListener() { public void mouseClicked(MouseEvent e) { redispatchMouseEvent(glassPane, e); } public void mouseEntered(MouseEvent e) { redispatchMouseEvent(glassPane, e); } public void mouseExited(MouseEvent e) { redispatchMouseEvent(glassPane, e); } public void mousePressed(MouseEvent e) { redispatchMouseEvent(glassPane, e); } public void mouseReleased(MouseEvent e) { redispatchMouseEvent(glassPane, e); } }); Point securityLabelPoint = securityStatusLabel.getLocation(); Point newPoint = SwingUtilities.convertPoint( securityStatusLabel.getParent(), securityLabelPoint.x, securityLabelPoint.y, callFrame); securityPanel.setBeginPoint(new Point((int) newPoint.getX() + 15, 0)); securityPanel.setBounds(0, (int) newPoint.getY() - 5, this.getWidth(), 130); glassPane.add(securityPanel); // Need to show the security panel explicitly in order to keep the // fade effect. securityPanel.setVisible(true); glassPane.setVisible(true); glassPane.addComponentListener( new ComponentAdapter() { /** Invoked when the component's size changes. */ @Override public void componentResized(ComponentEvent e) { if (glassPane.isVisible()) { glassPane.setVisible(false); callFrame.removeComponentListener(this); } } }); } } /** * Updates this view i.e. <tt>OneToOneCallPeerPanel</tt> so that it depicts the current state of * its model i.e. <tt>callPeer</tt>. */ private void updateViewFromModel() { /* * We receive events/notifications from various threads and we respond * to them in the AWT event dispatching thread. It is possible to first * schedule an event to be brought to the AWT event dispatching thread, * then to have #dispose() invoked on this instance and, finally, to * receive the scheduled event in the AWT event dispatching thread. In * such a case, this disposed instance should not respond to the event * because it may, for example, steal a visual Components depicting * video (which cannot belong to more than one parent at a time) from * another non-disposed OneToOneCallPeerPanel. */ if (!disposed) { if (SwingUtilities.isEventDispatchThread()) updateViewFromModelInEventDispatchThread(); else { SwingUtilities.invokeLater(updateViewFromModelInEventDispatchThread); } } } /** * Updates this view i.e. <tt>OneToOneCallPeerPanel</tt> so that it depicts the current state of * its model i.e. <tt>callPeer</tt>. The update is performed in the AWT event dispatching thread. */ private void updateViewFromModelInEventDispatchThread() { /* * We receive events/notifications from various threads and we respond * to them in the AWT event dispatching thread. It is possible to first * schedule an event to be brought to the AWT event dispatching thread, * then to have #dispose() invoked on this instance and, finally, to * receive the scheduled event in the AWT event dispatching thread. In * such a case, this disposed instance should not respond to the event * because it may, for example, steal a visual Components depicting * video (which cannot belong to more than one parent at a time) from * another non-disposed OneToOneCallPeerPanel. */ if (disposed) return; /* * Update the display of visual <tt>Component</tt>s depicting video * streaming between the local peer/user and the remote peer(s). */ OperationSetVideoTelephony videoTelephony = callPeer.getProtocolProvider().getOperationSet(OperationSetVideoTelephony.class); Component remoteVideo = null; Component localVideo = null; if (videoTelephony != null) { List<Component> remoteVideos = videoTelephony.getVisualComponents(callPeer); if ((remoteVideos != null) && !remoteVideos.isEmpty()) { /* * TODO OneToOneCallPeerPanel displays a one-to-one conversation * between the local peer/user and a specific remote peer. If * the remote peer is the focus of a telephony conference of its * own, it may be sending multiple videos to the local peer. * Switching to a user interface which displays multiple videos * is the responsibility of whoever decided that this * OneToOneCallPeerPanel is to be used to depict the current * state of the CallConference associated with the CallPeer * depicted by this instance. If that switching decides that * this instance is to continue being the user interface, then * we should probably pick up the remote video which is * generated by the remote peer and not one of its * ConferenceMembers. */ remoteVideo = remoteVideos.get(0); } if (uiVideoHandler.isLocalVideoVisible()) { try { localVideo = videoTelephony.getLocalVisualComponent(callPeer); } catch (OperationFailedException ofe) { /* * Well, we cannot do much about the exception. We'll just * not display the local video. */ logger.warn("Failed to retrieve local video to be displayed.", ofe); } } /* * Determine whether there is actually a change in the local and * remote videos which requires an update. */ boolean localVideoChanged = ((localVideo != this.localVideo) || ((localVideo != null) && !UIVideoHandler2.isAncestor(center, localVideo))); boolean remoteVideoChanged = ((remoteVideo != this.remoteVideo) || ((remoteVideo != null) && !UIVideoHandler2.isAncestor(center, remoteVideo))); // If the remote video has changed, maybe the CallPanel can display // the LO/SD/HD button. if (remoteVideoChanged) { // Updates video component which may listen the mouse and key // events. if (desktopSharingMouseAndKeyboardListener != null) { desktopSharingMouseAndKeyboardListener.setVideoComponent(remoteVideo); } CallPanel callPanel = callRenderer.getCallContainer(); // The remote video has been added, then tries to display the // LO/SD/HD button. if (remoteVideo != null) { callPanel.addRemoteVideoSpecificComponents(callPeer); } // The remote video has been removed, then hide the LO/SD/HD // button if it is currently displayed. else { callPanel.removeRemoteVideoSpecificComponents(); } } if (localVideoChanged || remoteVideoChanged) { /* * VideoContainer and JAWTRenderer cannot handle random * additions of Components. Removing the localVideo when the * user has requests its hiding though, should work without * removing all Components from the VideoCotainer and adding * them again. */ if (localVideoChanged && !remoteVideoChanged && (localVideo == null)) { if (this.localVideo != null) { center.remove(this.localVideo); this.localVideo = null; if (closeLocalVisualComponentButton != null) center.remove(closeLocalVisualComponentButton); } } else { center.removeAll(); this.localVideo = null; this.remoteVideo = null; /* * AWT does not make a guarantee about the Z order even * within an operating system i.e. the order of adding the * Components to their Container does not mean that they * will be determinedly painted in that or reverse order. * Anyway, there appears to be an expectation among the * developers less acquainted with AWT that AWT paints the * Components of a Container in an order that is the reverse * of the order of their adding. In order to satisfy that * expectation and thus give at least some idea to the * developers reading the code bellow, do add the Components * according to that expectation. */ if (localVideo != null) { if (closeLocalVisualComponentButton == null) { closeLocalVisualComponentButton = new CloseLocalVisualComponentButton(uiVideoHandler); } center.add(closeLocalVisualComponentButton, VideoLayout.CLOSE_LOCAL_BUTTON, -1); center.add(localVideo, VideoLayout.LOCAL, -1); this.localVideo = localVideo; } if (remoteVideo != null) { center.add(remoteVideo, VideoLayout.CENTER_REMOTE, -1); this.remoteVideo = remoteVideo; } } } } } /** The <tt>TransparentPanel</tt> that will display the peer status. */ private static class PeerStatusPanel extends TransparentPanel { /** * Silence the serial warning. Though there isn't a plan to serialize the instances of the * class, there're no fields so the default serialization routine will work. */ private static final long serialVersionUID = 0L; /** * Constructs a new <tt>PeerStatusPanel</tt>. * * @param layout the <tt>LayoutManager</tt> to use */ public PeerStatusPanel(LayoutManager layout) { super(layout); } /** @{inheritDoc} */ @Override public void paintComponent(Graphics g) { super.paintComponent(g); g = g.create(); try { AntialiasingManager.activateAntialiasing(g); g.setColor(Color.DARK_GRAY); g.fillRoundRect(0, 0, this.getWidth(), this.getHeight(), 10, 10); } finally { g.dispose(); } } } }
/** This class starts/stops/monitors the persistent requests on Freenet 0.7. */ public class PersistenceManager implements IFcpPersistentRequestsHandler { // FIXME Problem: positiv abgleich klappt, aber woher weiss ich wann LIST durch ist um zu // checken ob welche fehlen? private static final Logger logger = Logger.getLogger(PersistenceManager.class.getName()); // this would belong to the models, but its not needed there without persistence, hence we // maintain it here private final Hashtable<String, FrostUploadItem> uploadModelItems = new Hashtable<String, FrostUploadItem>(); private final Hashtable<String, FrostDownloadItem> downloadModelItems = new Hashtable<String, FrostDownloadItem>(); private final UploadModel uploadModel; private final DownloadModel downloadModel; private final DirectTransferQueue directTransferQueue; private final DirectTransferThread directTransferThread; private boolean showExternalItemsDownload; private boolean showExternalItemsUpload; private boolean isConnected = true; // we start in connected state private final FcpPersistentQueue persistentQueue; private final FcpListenThreadConnection fcpConn; private final FcpMultiRequestConnectionFileTransferTools fcpTools; private final Set<String> directGETsInProgress = new HashSet<String>(); private final Set<String> directPUTsInProgress = new HashSet<String>(); private final Set<String> directPUTsWithoutAnswer = new HashSet<String>(); /** @return true if Frost is configured to use persistent uploads and downloads, false if not */ public static boolean isPersistenceEnabled() { return Core.frostSettings.getBoolValue(SettingsClass.FCP2_USE_PERSISTENCE); } /** Must be called after the upload and download model is initialized! */ public PersistenceManager(final UploadModel um, final DownloadModel dm) throws Throwable { showExternalItemsDownload = Core.frostSettings.getBoolValue(SettingsClass.GQ_SHOW_EXTERNAL_ITEMS_DOWNLOAD); showExternalItemsUpload = Core.frostSettings.getBoolValue(SettingsClass.GQ_SHOW_EXTERNAL_ITEMS_UPLOAD); if (FcpHandler.inst().getFreenetNode() == null) { throw new Exception("No freenet nodes defined"); } final NodeAddress na = FcpHandler.inst().getFreenetNode(); fcpConn = FcpListenThreadConnection.createInstance(na); fcpTools = new FcpMultiRequestConnectionFileTransferTools(fcpConn); Core.frostSettings.addPropertyChangeListener( new PropertyChangeListener() { public void propertyChange(final PropertyChangeEvent evt) { if (evt.getPropertyName().equals(SettingsClass.GQ_SHOW_EXTERNAL_ITEMS_DOWNLOAD)) { showExternalItemsDownload = Core.frostSettings.getBoolValue(SettingsClass.GQ_SHOW_EXTERNAL_ITEMS_DOWNLOAD); if (showExternalItemsDownload) { // get external items showExternalDownloadItems(); } } else if (evt.getPropertyName().equals(SettingsClass.GQ_SHOW_EXTERNAL_ITEMS_UPLOAD)) { showExternalItemsUpload = Core.frostSettings.getBoolValue(SettingsClass.GQ_SHOW_EXTERNAL_ITEMS_UPLOAD); if (showExternalItemsUpload) { // get external items showExternalUploadItems(); } } } }); uploadModel = um; downloadModel = dm; // initially get all items from model for (int x = 0; x < uploadModel.getItemCount(); x++) { final FrostUploadItem ul = (FrostUploadItem) uploadModel.getItemAt(x); if (ul.getGqIdentifier() != null) { uploadModelItems.put(ul.getGqIdentifier(), ul); } } for (int x = 0; x < downloadModel.getItemCount(); x++) { final FrostDownloadItem ul = (FrostDownloadItem) downloadModel.getItemAt(x); if (ul.getGqIdentifier() != null) { downloadModelItems.put(ul.getGqIdentifier(), ul); } } // enqueue listeners to keep updated about the model items uploadModel.addOrderedModelListener( new SortedModelListener<FrostUploadItem>() { public void modelCleared() { for (final FrostUploadItem ul : uploadModelItems.values()) { if (ul.isExternal() == false) { fcpTools.removeRequest(ul.getGqIdentifier()); } } uploadModelItems.clear(); } public void itemAdded(final int position, final FrostUploadItem item) { uploadModelItems.put(item.getGqIdentifier(), item); if (!item.isExternal()) { // maybe start immediately startNewUploads(); } } public void itemChanged(final int position, final FrostUploadItem item) {} public void itemsRemoved(final int[] positions, final List<FrostUploadItem> items) { for (final FrostUploadItem item : items) { uploadModelItems.remove(item.getGqIdentifier()); if (item.isExternal() == false) { fcpTools.removeRequest(item.getGqIdentifier()); } } } }); downloadModel.addOrderedModelListener( new SortedModelListener<FrostDownloadItem>() { public void modelCleared() { for (final FrostDownloadItem ul : downloadModelItems.values()) { if (ul.isExternal() == false) { fcpTools.removeRequest(ul.getGqIdentifier()); } } downloadModelItems.clear(); } public void itemAdded(final int position, final FrostDownloadItem item) { downloadModelItems.put(item.getGqIdentifier(), item); if (!item.isExternal()) { // maybe start immediately startNewDownloads(); } } public void itemChanged(final int position, final FrostDownloadItem item) {} public void itemsRemoved(final int[] positions, final List<FrostDownloadItem> items) { for (final FrostDownloadItem item : items) { downloadModelItems.remove(item.getGqIdentifier()); if (item.isExternal() == false) { fcpTools.removeRequest(item.getGqIdentifier()); } } } }); directTransferQueue = new DirectTransferQueue(); directTransferThread = new DirectTransferThread(); persistentQueue = new FcpPersistentQueue(fcpTools, this); } public FcpMultiRequestConnectionFileTransferTools getFcpTools() { return fcpTools; } public void startThreads() { directTransferThread.start(); persistentQueue.startThreads(); final TimerTask task = new TimerTask() { @Override public void run() { maybeStartRequests(); } }; Core.schedule(task, 3000, 3000); } public void removeRequests(final List<String> requests) { for (final String id : requests) { fcpTools.removeRequest(id); } } /** * @param dlItem items whose global identifier is to check * @return true if this item is currently in the global queue, no matter in what state */ public boolean isItemInGlobalQueue(final FrostDownloadItem dlItem) { return persistentQueue.isIdInGlobalQueue(dlItem.getGqIdentifier()); } /** * @param ulItem items whose global identifier is to check * @return true if this item is currently in the global queue, no matter in what state */ public boolean isItemInGlobalQueue(final FrostUploadItem ulItem) { return persistentQueue.isIdInGlobalQueue(ulItem.getGqIdentifier()); } /** * Periodically check if we could start a new request. This could be done better if we check if a * request finished, but later... */ private void maybeStartRequests() { // start new requests startNewUploads(); startNewDownloads(); } public void connected() { isConnected = true; MainFrame.getInstance().setConnected(); logger.severe("now connected"); } public void disconnected() { isConnected = false; MainFrame.getInstance().setDisconnected(); SwingUtilities.invokeLater( new Runnable() { public void run() { uploadModel.removeExternalUploads(); downloadModel.removeExternalDownloads(); } }); logger.severe("disconnected!"); } public boolean isConnected() { return isConnected; } /** * Enqueue a direct GET if not already enqueued, or already downloaded to download dir. * * @return true if item was enqueued */ public boolean maybeEnqueueDirectGet( final FrostDownloadItem dlItem, final long expectedFileSize) { if (!isDirectTransferInProgress(dlItem)) { final File targetFile = new File(dlItem.getDownloadFilename()); if (!targetFile.isFile() || targetFile.length() != expectedFileSize) { directTransferQueue.appendItemToQueue(dlItem); return true; } } return false; } private void applyPriority(final FrostDownloadItem dlItem, final FcpPersistentGet getReq) { // apply externally changed priority if (dlItem.getPriority() != getReq.getPriority()) { if (Core.frostSettings.getBoolValue(SettingsClass.FCP2_ENFORCE_FROST_PRIO_FILE_DOWNLOAD)) { // reset priority with our current value fcpTools.changeRequestPriority(getReq.getIdentifier(), dlItem.getPriority()); } else { // apply to downloaditem dlItem.setPriority(getReq.getPriority()); } } } /** Apply the states of FcpRequestGet to the FrostDownloadItem. */ private void applyState(final FrostDownloadItem dlItem, final FcpPersistentGet getReq) { // when cancelled and we expect this, don't set failed; don't even set the old priority! if (dlItem.isInternalRemoveExpected() && getReq.isFailed()) { final int returnCode = getReq.getCode(); if (returnCode == 25) { return; } } applyPriority(dlItem, getReq); if (dlItem.isDirect() != getReq.isDirect()) { dlItem.setDirect(getReq.isDirect()); } if (!getReq.isProgressSet() && !getReq.isSuccess() && !getReq.isFailed()) { if (dlItem.getState() == FrostDownloadItem.STATE_WAITING) { dlItem.setState(FrostDownloadItem.STATE_PROGRESS); } return; } if (getReq.isProgressSet()) { final int doneBlocks = getReq.getDoneBlocks(); final int requiredBlocks = getReq.getRequiredBlocks(); final int totalBlocks = getReq.getTotalBlocks(); final boolean isFinalized = getReq.isFinalized(); if (totalBlocks > 0) { dlItem.setDoneBlocks(doneBlocks); dlItem.setRequiredBlocks(requiredBlocks); dlItem.setTotalBlocks(totalBlocks); dlItem.setFinalized(isFinalized); dlItem.fireValueChanged(); } if (dlItem.getState() != FrostDownloadItem.STATE_PROGRESS) { dlItem.setState(FrostDownloadItem.STATE_PROGRESS); } } if (getReq.isSuccess()) { // maybe progress was not completely sent dlItem.setFinalized(true); if (dlItem.getTotalBlocks() > 0 && dlItem.getDoneBlocks() < dlItem.getRequiredBlocks()) { dlItem.setDoneBlocks(dlItem.getRequiredBlocks()); dlItem.fireValueChanged(); } if (dlItem.isExternal()) { dlItem.setFileSize(getReq.getFilesize()); dlItem.setState(FrostDownloadItem.STATE_DONE); } else { if (dlItem.isDirect()) { maybeEnqueueDirectGet(dlItem, getReq.getFilesize()); } else { final FcpResultGet result = new FcpResultGet(true); final File targetFile = new File(dlItem.getDownloadFilename()); FileTransferManager.inst() .getDownloadManager() .notifyDownloadFinished(dlItem, result, targetFile); } } } if (getReq.isFailed()) { final String desc = getReq.getCodeDesc(); if (dlItem.isExternal()) { dlItem.setState(FrostDownloadItem.STATE_FAILED); dlItem.setErrorCodeDescription(desc); } else { final int returnCode = getReq.getCode(); final boolean isFatal = getReq.isFatal(); final String redirectURI = getReq.getRedirectURI(); final FcpResultGet result = new FcpResultGet(false, returnCode, desc, isFatal, redirectURI); final File targetFile = new File(dlItem.getDownloadFilename()); final boolean retry = FileTransferManager.inst() .getDownloadManager() .notifyDownloadFinished(dlItem, result, targetFile); if (retry) { fcpTools.removeRequest(getReq.getIdentifier()); startDownload(dlItem); // restart immediately } } } } /** Apply the states of FcpRequestPut to the FrostUploadItem. */ private void applyState(final FrostUploadItem ulItem, final FcpPersistentPut putReq) { // when cancelled and we expect this, don't set failed; don't even set the old priority! if (ulItem.isInternalRemoveExpected() && putReq.isFailed()) { final int returnCode = putReq.getCode(); if (returnCode == 25) { return; } } if (directPUTsWithoutAnswer.contains(ulItem.getGqIdentifier())) { // we got an answer directPUTsWithoutAnswer.remove(ulItem.getGqIdentifier()); } // apply externally changed priority if (ulItem.getPriority() != putReq.getPriority()) { if (Core.frostSettings.getBoolValue(SettingsClass.FCP2_ENFORCE_FROST_PRIO_FILE_UPLOAD)) { // reset priority with our current value fcpTools.changeRequestPriority(putReq.getIdentifier(), ulItem.getPriority()); } else { // apply to uploaditem ulItem.setPriority(putReq.getPriority()); } } if (!putReq.isProgressSet() && !putReq.isSuccess() && !putReq.isFailed()) { if (ulItem.getState() == FrostUploadItem.STATE_WAITING) { ulItem.setState(FrostUploadItem.STATE_PROGRESS); } return; } if (putReq.isProgressSet()) { final int doneBlocks = putReq.getDoneBlocks(); final int totalBlocks = putReq.getTotalBlocks(); final boolean isFinalized = putReq.isFinalized(); if (totalBlocks > 0) { ulItem.setDoneBlocks(doneBlocks); ulItem.setTotalBlocks(totalBlocks); ulItem.setFinalized(isFinalized); ulItem.fireValueChanged(); } if (ulItem.getState() != FrostUploadItem.STATE_PROGRESS) { ulItem.setState(FrostUploadItem.STATE_PROGRESS); } } if (putReq.isSuccess()) { // maybe progress was not completely sent ulItem.setFinalized(true); if (ulItem.getTotalBlocks() > 0 && ulItem.getDoneBlocks() != ulItem.getTotalBlocks()) { ulItem.setDoneBlocks(ulItem.getTotalBlocks()); } final String chkKey = putReq.getUri(); if (ulItem.isExternal()) { ulItem.setState(FrostUploadItem.STATE_DONE); ulItem.setKey(chkKey); } else { final FcpResultPut result = new FcpResultPut(FcpResultPut.Success, chkKey); FileTransferManager.inst().getUploadManager().notifyUploadFinished(ulItem, result); } } if (putReq.isFailed()) { final String desc = putReq.getCodeDesc(); if (ulItem.isExternal()) { ulItem.setState(FrostUploadItem.STATE_FAILED); ulItem.setErrorCodeDescription(desc); } else { final int returnCode = putReq.getCode(); final boolean isFatal = putReq.isFatal(); final FcpResultPut result; if (returnCode == 9) { result = new FcpResultPut(FcpResultPut.KeyCollision, returnCode, desc, isFatal); } else if (returnCode == 5) { result = new FcpResultPut(FcpResultPut.Retry, returnCode, desc, isFatal); } else { result = new FcpResultPut(FcpResultPut.Error, returnCode, desc, isFatal); } FileTransferManager.inst().getUploadManager().notifyUploadFinished(ulItem, result); } } } private void startNewUploads() { boolean isLimited = true; int currentAllowedUploadCount = 0; { final int allowedConcurrentUploads = Core.frostSettings.getIntValue(SettingsClass.UPLOAD_MAX_THREADS); if (allowedConcurrentUploads <= 0) { isLimited = false; } else { int runningUploads = 0; for (final FrostUploadItem ulItem : uploadModelItems.values()) { if (!ulItem.isExternal() && ulItem.getState() == FrostUploadItem.STATE_PROGRESS) { runningUploads++; } } currentAllowedUploadCount = allowedConcurrentUploads - runningUploads; if (currentAllowedUploadCount < 0) { currentAllowedUploadCount = 0; } } } { while (!isLimited || currentAllowedUploadCount > 0) { final FrostUploadItem ulItem = FileTransferManager.inst().getUploadManager().selectNextUploadItem(); if (ulItem == null) { break; } if (startUpload(ulItem)) { currentAllowedUploadCount--; } } } } public boolean startUpload(final FrostUploadItem ulItem) { if (ulItem == null || ulItem.getState() != FrostUploadItem.STATE_WAITING) { return false; } ulItem.setUploadStartedMillis(System.currentTimeMillis()); ulItem.setState(FrostUploadItem.STATE_PROGRESS); // start the upload final boolean doMime; final boolean setTargetFileName; if (ulItem.isSharedFile()) { doMime = false; setTargetFileName = false; } else { doMime = true; setTargetFileName = true; } // try to start using DDA boolean isDda = fcpTools.startPersistentPutUsingDda( ulItem.getGqIdentifier(), ulItem.getFile(), ulItem.getFileName(), doMime, setTargetFileName, ulItem.getCompress(), ulItem.getFreenetCompatibilityMode(), ulItem.getPriority()); if (!isDda) { // upload was not startet because DDA is not allowed... // if UploadManager selected this file then it is not already in progress! directTransferQueue.appendItemToQueue(ulItem); } return true; } private void startNewDownloads() { boolean isLimited = true; int currentAllowedDownloadCount = 0; { final int allowedConcurrentDownloads = Core.frostSettings.getIntValue(SettingsClass.DOWNLOAD_MAX_THREADS); if (allowedConcurrentDownloads <= 0) { isLimited = false; } else { int runningDownloads = 0; for (final FrostDownloadItem dlItem : downloadModelItems.values()) { if (!dlItem.isExternal() && dlItem.getState() == FrostDownloadItem.STATE_PROGRESS) { runningDownloads++; } } currentAllowedDownloadCount = allowedConcurrentDownloads - runningDownloads; if (currentAllowedDownloadCount < 0) { currentAllowedDownloadCount = 0; } } } { while (!isLimited || currentAllowedDownloadCount > 0) { final FrostDownloadItem dlItem = FileTransferManager.inst().getDownloadManager().selectNextDownloadItem(); if (dlItem == null) { break; } // start the download if (startDownload(dlItem)) { currentAllowedDownloadCount--; } } } } public boolean startDownload(final FrostDownloadItem dlItem) { if (dlItem == null || dlItem.getState() != FrostDownloadItem.STATE_WAITING) { return false; } dlItem.setDownloadStartedTime(System.currentTimeMillis()); dlItem.setState(FrostDownloadItem.STATE_PROGRESS); final String gqid = dlItem.getGqIdentifier(); final File targetFile = new File(dlItem.getDownloadFilename()); boolean isDda = fcpTools.startPersistentGet(dlItem.getKey(), gqid, targetFile, dlItem.getPriority()); dlItem.setDirect(!isDda); return true; } private void showExternalUploadItems() { final Map<String, FcpPersistentPut> items = persistentQueue.getUploadRequests(); for (final FcpPersistentPut uploadRequest : items.values()) { if (!uploadModelItems.containsKey(uploadRequest.getIdentifier())) { addExternalItem(uploadRequest); } } } private void showExternalDownloadItems() { final Map<String, FcpPersistentGet> items = persistentQueue.getDownloadRequests(); for (final FcpPersistentGet downloadRequest : items.values()) { if (!downloadModelItems.containsKey(downloadRequest.getIdentifier())) { addExternalItem(downloadRequest); } } } private void addExternalItem(final FcpPersistentPut uploadRequest) { final FrostUploadItem ulItem = new FrostUploadItem(); ulItem.setGqIdentifier(uploadRequest.getIdentifier()); ulItem.setExternal(true); // direct uploads maybe have no filename, use identifier String fileName = uploadRequest.getFilename(); if (fileName == null) { fileName = uploadRequest.getIdentifier(); } else if (fileName.indexOf('/') > -1 || fileName.indexOf('\\') > -1) { // filename contains directories, use only filename final String stmp = new File(fileName).getName(); if (stmp.length() > 0) { fileName = stmp; // use plain filename } } ulItem.setFile(new File(fileName)); ulItem.setFileName(fileName); ulItem.setFileSize(uploadRequest.getFileSize()); ulItem.setPriority(uploadRequest.getPriority()); ulItem.setState(FrostUploadItem.STATE_PROGRESS); SwingUtilities.invokeLater( new Runnable() { public void run() { uploadModel.addExternalItem(ulItem); } }); applyState(ulItem, uploadRequest); } private void addExternalItem(final FcpPersistentGet downloadRequest) { // direct downloads maybe have no filename, use identifier String fileName = downloadRequest.getFilename(); if (fileName == null) { fileName = downloadRequest.getIdentifier(); } else if (fileName.indexOf('/') > -1 || fileName.indexOf('\\') > -1) { // filename contains directories, use only filename final String stmp = new File(fileName).getName(); if (stmp.length() > 0) { fileName = stmp; // use plain filename } } final FrostDownloadItem dlItem = new FrostDownloadItem(fileName, downloadRequest.getUri()); dlItem.setExternal(true); dlItem.setGqIdentifier(downloadRequest.getIdentifier()); dlItem.setState(FrostDownloadItem.STATE_PROGRESS); SwingUtilities.invokeLater( new Runnable() { public void run() { downloadModel.addExternalItem(dlItem); } }); applyState(dlItem, downloadRequest); } public boolean isDirectTransferInProgress(final FrostDownloadItem dlItem) { final String id = dlItem.getGqIdentifier(); return directGETsInProgress.contains(id); } public boolean isDirectTransferInProgress(final FrostUploadItem ulItem) { final String id = ulItem.getGqIdentifier(); if (directPUTsInProgress.contains(id)) { return true; } if (directPUTsWithoutAnswer.contains(id)) { return true; } return false; } private class DirectTransferThread extends Thread { @Override public void run() { final int maxAllowedExceptions = 5; int catchedExceptions = 0; while (true) { try { // if there is no work in queue this call waits for a new queue item final ModelItem<?> item = directTransferQueue.getItemFromQueue(); if (item == null) { // paranoia, should never happen Mixed.wait(5 * 1000); continue; } if (item instanceof FrostUploadItem) { // transfer bytes to node final FrostUploadItem ulItem = (FrostUploadItem) item; // FIXME: provide item, state=Transfer to node, % shows progress final String gqid = ulItem.getGqIdentifier(); final boolean doMime; final boolean setTargetFileName; if (ulItem.isSharedFile()) { doMime = false; setTargetFileName = false; } else { doMime = true; setTargetFileName = true; } final NodeMessage answer = fcpTools.startDirectPersistentPut( gqid, ulItem.getFile(), ulItem.getFileName(), doMime, setTargetFileName, ulItem.getCompress(), ulItem.getFreenetCompatibilityMode(), ulItem.getPriority()); if (answer == null) { final String desc = "Could not open a new FCP2 socket for direct put!"; final FcpResultPut result = new FcpResultPut(FcpResultPut.Error, -1, desc, false); FileTransferManager.inst().getUploadManager().notifyUploadFinished(ulItem, result); logger.severe(desc); } else { // wait for an answer, don't start request again directPUTsWithoutAnswer.add(gqid); } directPUTsInProgress.remove(gqid); } else if (item instanceof FrostDownloadItem) { // transfer bytes from node final FrostDownloadItem dlItem = (FrostDownloadItem) item; // FIXME: provide item, state=Transfer from node, % shows progress final String gqid = dlItem.getGqIdentifier(); final File targetFile = new File(dlItem.getDownloadFilename()); final boolean retryNow; NodeMessage answer = null; try { answer = fcpTools.startDirectPersistentGet(gqid, targetFile); } catch (final FileNotFoundException e) { final String msg = "Could not write to " + dlItem.getDownloadFilename() + ": " + e.getMessage(); System.out.println(msg); logger.severe(msg); } if (answer != null) { final FcpResultGet result = new FcpResultGet(true); FileTransferManager.inst() .getDownloadManager() .notifyDownloadFinished(dlItem, result, targetFile); retryNow = false; } else { logger.severe("Could not open a new fcp socket for direct get!"); final FcpResultGet result = new FcpResultGet(false); retryNow = FileTransferManager.inst() .getDownloadManager() .notifyDownloadFinished(dlItem, result, targetFile); } directGETsInProgress.remove(gqid); if (retryNow) { startDownload(dlItem); } } } catch (final Throwable t) { logger.log(Level.SEVERE, "Exception catched", t); catchedExceptions++; } if (catchedExceptions > maxAllowedExceptions) { logger.log(Level.SEVERE, "Stopping DirectTransferThread because of too much exceptions"); break; } } } } /** * A queue class that queues items waiting for its direct transfer (put to node or get from node). */ private class DirectTransferQueue { private final LinkedList<ModelItem<?>> queue = new LinkedList<ModelItem<?>>(); public synchronized ModelItem<?> getItemFromQueue() { try { // let dequeueing threads wait for work while (queue.isEmpty()) { wait(); } } catch (final InterruptedException e) { return null; // waiting abandoned } if (queue.isEmpty() == false) { return queue.removeFirst(); } return null; } public synchronized void appendItemToQueue(final FrostDownloadItem item) { final String id = item.getGqIdentifier(); directGETsInProgress.add(id); queue.addLast(item); notifyAll(); // notify all waiters (if any) of new record } public synchronized void appendItemToQueue(final FrostUploadItem item) { final String id = item.getGqIdentifier(); directPUTsInProgress.add(id); queue.addLast(item); notifyAll(); // notify all waiters (if any) of new record } } public void persistentRequestError(final String id, final NodeMessage nm) { if (uploadModelItems.containsKey(id)) { final FrostUploadItem item = uploadModelItems.get(id); item.setEnabled(false); item.setState(FrostUploadItem.STATE_FAILED); item.setErrorCodeDescription(nm.getStringValue("CodeDescription")); } else if (downloadModelItems.containsKey(id)) { final FrostDownloadItem item = downloadModelItems.get(id); item.setEnabled(false); item.setState(FrostDownloadItem.STATE_FAILED); item.setErrorCodeDescription(nm.getStringValue("CodeDescription")); } else { System.out.println("persistentRequestError: ID not in any model: " + id); } } public void persistentRequestAdded(final FcpPersistentPut uploadRequest) { final FrostUploadItem ulItem = uploadModelItems.get(uploadRequest.getIdentifier()); if (ulItem != null) { // own item added to global queue, or existing external item applyState(ulItem, uploadRequest); } else { if (showExternalItemsUpload) { addExternalItem(uploadRequest); } } } public void persistentRequestAdded(final FcpPersistentGet downloadRequest) { final FrostDownloadItem dlItem = downloadModelItems.get(downloadRequest.getIdentifier()); if (dlItem != null) { // own item added to global queue, or existing external item applyState(dlItem, downloadRequest); } else { if (showExternalItemsDownload) { addExternalItem(downloadRequest); } } } public void persistentRequestModified(final FcpPersistentPut uploadRequest) { if (uploadModelItems.containsKey(uploadRequest.getIdentifier())) { final FrostUploadItem ulItem = uploadModelItems.get(uploadRequest.getIdentifier()); ulItem.setPriority(uploadRequest.getPriority()); } } public void persistentRequestModified(final FcpPersistentGet downloadRequest) { if (downloadModelItems.containsKey(downloadRequest.getIdentifier())) { final FrostDownloadItem dlItem = downloadModelItems.get(downloadRequest.getIdentifier()); applyPriority(dlItem, downloadRequest); } } public void persistentRequestRemoved(final FcpPersistentPut uploadRequest) { if (uploadModelItems.containsKey(uploadRequest.getIdentifier())) { final FrostUploadItem ulItem = uploadModelItems.get(uploadRequest.getIdentifier()); if (ulItem.isExternal()) { SwingUtilities.invokeLater( new Runnable() { public void run() { List<FrostUploadItem> itemList = new ArrayList<FrostUploadItem>(); itemList.add(ulItem); uploadModel.removeItems(itemList); } }); } else { if (ulItem.isInternalRemoveExpected()) { ulItem.setInternalRemoveExpected(false); // clear flag } else if (ulItem.getState() != FrostUploadItem.STATE_DONE) { ulItem.setEnabled(false); ulItem.setState(FrostUploadItem.STATE_FAILED); ulItem.setErrorCodeDescription("Disappeared from global queue"); } } } } public void persistentRequestRemoved(final FcpPersistentGet downloadRequest) { if (downloadModelItems.containsKey(downloadRequest.getIdentifier())) { final FrostDownloadItem dlItem = downloadModelItems.get(downloadRequest.getIdentifier()); if (dlItem.isExternal()) { SwingUtilities.invokeLater( new Runnable() { public void run() { List<FrostDownloadItem> itemList = new ArrayList<FrostDownloadItem>(); itemList.add(dlItem); downloadModel.removeItems(itemList); } }); } else { if (dlItem.isInternalRemoveExpected()) { dlItem.setInternalRemoveExpected(false); // clear flag } else if (dlItem.getState() != FrostDownloadItem.STATE_DONE) { dlItem.setEnabled(false); dlItem.setState(FrostDownloadItem.STATE_FAILED); dlItem.setErrorCodeDescription("Disappeared from global queue"); } } } } public void persistentRequestUpdated(final FcpPersistentPut uploadRequest) { final FrostUploadItem ui = uploadModelItems.get(uploadRequest.getIdentifier()); if (ui == null) { // not (yet) in our model return; } applyState(ui, uploadRequest); } public void persistentRequestUpdated(final FcpPersistentGet downloadRequest) { final FrostDownloadItem dl = downloadModelItems.get(downloadRequest.getIdentifier()); if (dl == null) { // not (yet) in our model return; } applyState(dl, downloadRequest); } }
/** * The <tt>StatusSubMenu</tt> provides a menu which allow to select the status for each of the * protocol providers registered when the menu appears * * @author Nicolas Chamouard */ public class StatusSubMenu extends JMenu { /** A reference of <tt>Systray</tt> */ private SystrayServiceJdicImpl parentSystray; /** Contains all accounts and corresponding menus. */ private Hashtable accountSelectors = new Hashtable(); private Logger logger = Logger.getLogger(StatusSubMenu.class); /** * Creates an instance of <tt>StatusSubMenu</tt>. * * @param tray a reference of the parent <tt>Systray</tt> */ public StatusSubMenu(SystrayServiceJdicImpl tray) { parentSystray = tray; this.setText(Resources.getString("setStatus")); this.setIcon(Resources.getImage("statusMenuIcon")); /* makes the menu look better */ this.setPreferredSize(new java.awt.Dimension(28, 24)); this.init(); } /** * Adds the account corresponding to the given protocol provider to this menu. * * @param protocolProvider the protocol provider corresponding to the account to add */ private void addAccount(ProtocolProviderService protocolProvider) { OperationSetPresence presence = (OperationSetPresence) protocolProvider.getOperationSet(OperationSetPresence.class); if (presence == null) { StatusSimpleSelector simpleSelector = new StatusSimpleSelector(parentSystray, protocolProvider); this.accountSelectors.put(protocolProvider.getAccountID(), simpleSelector); this.add(simpleSelector); } else { StatusSelector statusSelector = new StatusSelector(parentSystray, protocolProvider, presence); this.accountSelectors.put(protocolProvider.getAccountID(), statusSelector); this.add(statusSelector); presence.addProviderPresenceStatusListener(new SystrayProviderPresenceStatusListener()); } } /** * Removes the account corresponding to the given protocol provider from this menu. * * @param protocolProvider the protocol provider corresponding to the account to remove. */ private void removeAccount(ProtocolProviderService protocolProvider) { Component c = (Component) this.accountSelectors.get(protocolProvider.getAccountID()); this.remove(c); } /** * We fill the protocolProviderTable with all running protocol providers at the start of the * bundle. */ private void init() { SystrayActivator.bundleContext.addServiceListener(new ProtocolProviderServiceListener()); ServiceReference[] protocolProviderRefs = null; try { protocolProviderRefs = SystrayActivator.bundleContext.getServiceReferences( ProtocolProviderService.class.getName(), null); } catch (InvalidSyntaxException ex) { // this shouldn't happen since we're providing no parameter string // but let's log just in case. logger.error("Error while retrieving service refs", ex); return; } // in case we found any if (protocolProviderRefs != null) { for (int i = 0; i < protocolProviderRefs.length; i++) { ProtocolProviderService provider = (ProtocolProviderService) SystrayActivator.bundleContext.getService(protocolProviderRefs[i]); boolean isHidden = provider.getAccountID().getAccountProperties().get("HIDDEN_PROTOCOL") != null; if (!isHidden) this.addAccount(provider); } } } /** * Listens for <tt>ServiceEvent</tt>s indicating that a <tt>ProtocolProviderService</tt> has been * registered and completes the account status menu. */ private class ProtocolProviderServiceListener implements ServiceListener { /** * When a service is registered or unregistered, we update the provider tables and add/remove * listeners (if it supports BasicInstantMessenging implementation) * * @param event ServiceEvent */ public void serviceChanged(ServiceEvent event) { // if the event is caused by a bundle being stopped, we don't want to // know if (event.getServiceReference().getBundle().getState() == Bundle.STOPPING) { return; } Object service = SystrayActivator.bundleContext.getService(event.getServiceReference()); if (!(service instanceof ProtocolProviderService)) return; ProtocolProviderService provider = (ProtocolProviderService) service; if (event.getType() == ServiceEvent.REGISTERED) addAccount(provider); if (event.getType() == ServiceEvent.UNREGISTERING) removeAccount(provider); } } /** * Listens for all providerStatusChanged and providerStatusMessageChanged events in order to * refresh the account status panel, when a status is changed. */ private class SystrayProviderPresenceStatusListener implements ProviderPresenceStatusListener { /** Fired when an account has changed its status. We update the icon in the menu. */ public void providerStatusChanged(ProviderPresenceStatusChangeEvent evt) { ProtocolProviderService pps = evt.getProvider(); StatusSelector selectorBox = (StatusSelector) accountSelectors.get(pps.getAccountID()); if (selectorBox == null) return; selectorBox.updateStatus(evt.getNewStatus()); } public void providerStatusMessageChanged(PropertyChangeEvent evt) {} } }
/** * The source contact service. The will show most recent messages. * * @author Damian Minkov */ public class MessageSourceService extends MetaContactListAdapter implements ContactSourceService, ContactPresenceStatusListener, ContactCapabilitiesListener, ProviderPresenceStatusListener, SubscriptionListener, LocalUserChatRoomPresenceListener, MessageListener, ChatRoomMessageListener, AdHocChatRoomMessageListener { /** The logger for this class. */ private static Logger logger = Logger.getLogger(MessageSourceService.class); /** The display name of this contact source. */ private final String MESSAGE_HISTORY_NAME; /** The type of the source service, the place to be shown in the ui. */ private int sourceServiceType = CONTACT_LIST_TYPE; /** * Whether to show recent messages in history or in contactlist. By default we show it in * contactlist. */ private static final String IN_HISTORY_PROPERTY = "net.java.sip.communicator.impl.msghistory.contactsrc.IN_HISTORY"; /** Property to control number of recent messages. */ private static final String NUMBER_OF_RECENT_MSGS_PROP = "net.java.sip.communicator.impl.msghistory.contactsrc.MSG_NUMBER"; /** Property to control version of recent messages. */ private static final String VER_OF_RECENT_MSGS_PROP = "net.java.sip.communicator.impl.msghistory.contactsrc.MSG_VER"; /** Property to control messages type. Can query for message sub type. */ private static final String IS_MESSAGE_SUBTYPE_SMS_PROP = "net.java.sip.communicator.impl.msghistory.contactsrc.IS_SMS_ENABLED"; /** * The number of recent messages to store in the history, but will retrieve just * <tt>numberOfMessages</tt> */ private static final int NUMBER_OF_MSGS_IN_HISTORY = 100; /** Number of messages to show. */ private int numberOfMessages = 10; /** The structure to save recent messages list. */ private static final String[] STRUCTURE_NAMES = new String[] {"provider", "contact", "timestamp", "ver"}; /** The current version of recent messages. When changed the recent messages are recreated. */ private static String RECENT_MSGS_VER = "2"; /** The structure. */ private static final HistoryRecordStructure recordStructure = new HistoryRecordStructure(STRUCTURE_NAMES); /** Recent messages history ID. */ private static final HistoryID historyID = HistoryID.createFromRawID(new String[] {"recent_messages"}); /** The cache for recent messages. */ private History history = null; /** List of recent messages. */ private final List<ComparableEvtObj> recentMessages = new LinkedList<ComparableEvtObj>(); /** Date of the oldest shown message. */ private Date oldestRecentMessage = null; /** The last query created. */ private MessageSourceContactQuery recentQuery = null; /** The message subtype if any. */ private boolean isSMSEnabled = false; /** Message history service that has created us. */ private MessageHistoryServiceImpl messageHistoryService; /** Constructs MessageSourceService. */ MessageSourceService(MessageHistoryServiceImpl messageHistoryService) { this.messageHistoryService = messageHistoryService; ConfigurationService conf = MessageHistoryActivator.getConfigurationService(); if (conf.getBoolean(IN_HISTORY_PROPERTY, false)) { sourceServiceType = HISTORY_TYPE; } MESSAGE_HISTORY_NAME = MessageHistoryActivator.getResources().getI18NString("service.gui.RECENT_MESSAGES"); numberOfMessages = conf.getInt(NUMBER_OF_RECENT_MSGS_PROP, numberOfMessages); isSMSEnabled = conf.getBoolean(IS_MESSAGE_SUBTYPE_SMS_PROP, isSMSEnabled); RECENT_MSGS_VER = conf.getString(VER_OF_RECENT_MSGS_PROP, RECENT_MSGS_VER); MessageSourceContactPresenceStatus.MSG_SRC_CONTACT_ONLINE.setStatusIcon( MessageHistoryActivator.getResources() .getImageInBytes("service.gui.icons.SMS_STATUS_ICON")); } /** * Returns the display name of this contact source. * * @return the display name of this contact source */ @Override public String getDisplayName() { return MESSAGE_HISTORY_NAME; } /** * Returns default type to indicate that this contact source can be queried by default filters. * * @return the type of this contact source */ @Override public int getType() { return sourceServiceType; } /** * Returns the index of the contact source in the result list. * * @return the index of the contact source in the result list */ @Override public int getIndex() { return 0; } /** * Creates query for the given <tt>searchString</tt>. * * @param queryString the string to search for * @return the created query */ @Override public ContactQuery createContactQuery(String queryString) { recentQuery = (MessageSourceContactQuery) createContactQuery(queryString, numberOfMessages); return recentQuery; } /** * Updates the contact sources in the recent query if any. Done here in order to sync with * recentMessages instance, and to check for already existing instances of contact sources. * Normally called from the query. */ public void updateRecentMessages() { if (recentQuery == null) return; synchronized (recentMessages) { List<SourceContact> currentContactsInQuery = recentQuery.getQueryResults(); for (ComparableEvtObj evtObj : recentMessages) { // the contains will use the correct equals method of // the object evtObj if (!currentContactsInQuery.contains(evtObj)) { MessageSourceContact newSourceContact = new MessageSourceContact(evtObj.getEventObject(), MessageSourceService.this); newSourceContact.initDetails(evtObj.getEventObject()); recentQuery.addQueryResult(newSourceContact); } } } } /** * Searches for entries in cached recent messages in history. * * @param provider the provider which contact messages we will search * @param isStatusChanged is the search because of status changed * @return entries in cached recent messages in history. */ private List<ComparableEvtObj> getCachedRecentMessages( ProtocolProviderService provider, boolean isStatusChanged) { String providerID = provider.getAccountID().getAccountUniqueID(); List<String> recentMessagesContactIDs = getRecentContactIDs( providerID, recentMessages.size() < numberOfMessages ? null : oldestRecentMessage); List<ComparableEvtObj> cachedRecentMessages = new ArrayList<ComparableEvtObj>(); for (String contactID : recentMessagesContactIDs) { Collection<EventObject> res = messageHistoryService.findRecentMessagesPerContact( numberOfMessages, providerID, contactID, isSMSEnabled); processEventObjects(res, cachedRecentMessages, isStatusChanged); } return cachedRecentMessages; } /** * Process list of event objects. Checks whether message source contact already exist for this * event object, if yes just update it with the new values (not sure whether we should do this, as * it may bring old messages) and if status of provider is changed, init its details, updates its * capabilities. It still adds the found messages source contact to the list of the new contacts, * as later we will detect this and fire update event. If nothing found a new contact is created. * * @param res list of event * @param cachedRecentMessages list of newly created source contacts or already existed but * updated with corresponding event object * @param isStatusChanged whether provider status changed and we are processing */ private void processEventObjects( Collection<EventObject> res, List<ComparableEvtObj> cachedRecentMessages, boolean isStatusChanged) { for (EventObject obj : res) { ComparableEvtObj oldMsg = findRecentMessage(obj, recentMessages); if (oldMsg != null) { oldMsg.update(obj); // update if (isStatusChanged && recentQuery != null) recentQuery.updateCapabilities(oldMsg, obj); // we still add it to cachedRecentMessages // later we will find it is duplicate and will fire // update event if (!cachedRecentMessages.contains(oldMsg)) cachedRecentMessages.add(oldMsg); continue; } oldMsg = findRecentMessage(obj, cachedRecentMessages); if (oldMsg == null) { oldMsg = new ComparableEvtObj(obj); if (isStatusChanged && recentQuery != null) recentQuery.updateCapabilities(oldMsg, obj); cachedRecentMessages.add(oldMsg); } } } /** * Access for source contacts impl. * * @return */ boolean isSMSEnabled() { return isSMSEnabled; } /** * Add the ComparableEvtObj, newly added will fire new, for existing fire update and when trimming * the list to desired length fire remove for those that were removed * * @param contactsToAdd */ private void addNewRecentMessages(List<ComparableEvtObj> contactsToAdd) { // now find object to fire new, and object to fire remove // let us find duplicates and fire update List<ComparableEvtObj> duplicates = new ArrayList<ComparableEvtObj>(); for (ComparableEvtObj msgToAdd : contactsToAdd) { if (recentMessages.contains(msgToAdd)) { duplicates.add(msgToAdd); // save update updateRecentMessageToHistory(msgToAdd); } } recentMessages.removeAll(duplicates); // now contacts to add has no duplicates, add them all boolean changed = recentMessages.addAll(contactsToAdd); if (changed) { Collections.sort(recentMessages); if (recentQuery != null) { for (ComparableEvtObj obj : duplicates) recentQuery.updateContact(obj, obj.getEventObject()); } } if (!recentMessages.isEmpty()) oldestRecentMessage = recentMessages.get(recentMessages.size() - 1).getTimestamp(); // trim List<ComparableEvtObj> removedItems = null; if (recentMessages.size() > numberOfMessages) { removedItems = new ArrayList<ComparableEvtObj>( recentMessages.subList(numberOfMessages, recentMessages.size())); recentMessages.removeAll(removedItems); } if (recentQuery != null) { // now fire, removed for all that were in the list // and now are removed after trim if (removedItems != null) { for (ComparableEvtObj msc : removedItems) { if (!contactsToAdd.contains(msc)) recentQuery.fireContactRemoved(msc); } } // fire new for all that were added, and not removed after trim for (ComparableEvtObj msc : contactsToAdd) { if ((removedItems == null || !removedItems.contains(msc)) && !duplicates.contains(msc)) { MessageSourceContact newSourceContact = new MessageSourceContact(msc.getEventObject(), MessageSourceService.this); newSourceContact.initDetails(msc.getEventObject()); recentQuery.addQueryResult(newSourceContact); } } // if recent messages were changed, indexes have change lets // fire event for the last element which will reorder the whole // group if needed. if (changed) recentQuery.fireContactChanged(recentMessages.get(recentMessages.size() - 1)); } } /** * When a provider is added, do not block and start executing in new thread. * * @param provider ProtocolProviderService */ void handleProviderAdded(final ProtocolProviderService provider, final boolean isStatusChanged) { new Thread( new Runnable() { @Override public void run() { handleProviderAddedInSeparateThread(provider, isStatusChanged); } }) .start(); } /** * When a provider is added. As searching can be slow especially when handling special type of * messages (with subType) this need to be run in new Thread. * * @param provider ProtocolProviderService */ private void handleProviderAddedInSeparateThread( ProtocolProviderService provider, boolean isStatusChanged) { // lets check if we have cached recent messages for this provider, and // fire events if found and are newer synchronized (recentMessages) { List<ComparableEvtObj> cachedRecentMessages = getCachedRecentMessages(provider, isStatusChanged); if (cachedRecentMessages.isEmpty()) { // maybe there is no cached history for this // let's check // load it not from cache, but do a local search Collection<EventObject> res = messageHistoryService.findRecentMessagesPerContact( numberOfMessages, provider.getAccountID().getAccountUniqueID(), null, isSMSEnabled); List<ComparableEvtObj> newMsc = new ArrayList<ComparableEvtObj>(); processEventObjects(res, newMsc, isStatusChanged); addNewRecentMessages(newMsc); for (ComparableEvtObj msc : newMsc) { saveRecentMessageToHistory(msc); } } else addNewRecentMessages(cachedRecentMessages); } } /** * Tries to match the event object to already existing ComparableEvtObj in the supplied list. * * @param obj the object that we will try to match. * @param list the list we will search in. * @return the found ComparableEvtObj */ private static ComparableEvtObj findRecentMessage(EventObject obj, List<ComparableEvtObj> list) { Contact contact = null; ChatRoom chatRoom = null; if (obj instanceof MessageDeliveredEvent) { contact = ((MessageDeliveredEvent) obj).getDestinationContact(); } else if (obj instanceof MessageReceivedEvent) { contact = ((MessageReceivedEvent) obj).getSourceContact(); } else if (obj instanceof ChatRoomMessageDeliveredEvent) { chatRoom = ((ChatRoomMessageDeliveredEvent) obj).getSourceChatRoom(); } else if (obj instanceof ChatRoomMessageReceivedEvent) { chatRoom = ((ChatRoomMessageReceivedEvent) obj).getSourceChatRoom(); } for (ComparableEvtObj evt : list) { if ((contact != null && contact.equals(evt.getContact())) || (chatRoom != null && chatRoom.equals(evt.getRoom()))) return evt; } return null; } /** * A provider has been removed. * * @param provider the ProtocolProviderService that has been unregistered. */ void handleProviderRemoved(ProtocolProviderService provider) { // lets remove the recent messages for this provider, and update // with recent messages for the available providers synchronized (recentMessages) { if (provider != null) { List<ComparableEvtObj> removedItems = new ArrayList<ComparableEvtObj>(); for (ComparableEvtObj msc : recentMessages) { if (msc.getProtocolProviderService().equals(provider)) removedItems.add(msc); } recentMessages.removeAll(removedItems); if (!recentMessages.isEmpty()) oldestRecentMessage = recentMessages.get(recentMessages.size() - 1).getTimestamp(); else oldestRecentMessage = null; if (recentQuery != null) { for (ComparableEvtObj msc : removedItems) { recentQuery.fireContactRemoved(msc); } } } // handleProviderRemoved can be invoked due to stopped // history service, if this is the case we do not want to // update messages if (!this.messageHistoryService.isHistoryLoggingEnabled()) return; // lets do the same as we enable provider // for all registered providers and finally fire events List<ComparableEvtObj> contactsToAdd = new ArrayList<ComparableEvtObj>(); for (ProtocolProviderService pps : messageHistoryService.getCurrentlyAvailableProviders()) { contactsToAdd.addAll(getCachedRecentMessages(pps, true)); } addNewRecentMessages(contactsToAdd); } } /** * Searches for contact ids in history of recent messages. * * @param provider * @param after * @return */ List<String> getRecentContactIDs(String provider, Date after) { List<String> res = new ArrayList<String>(); try { History history = getHistory(); if (history != null) { Iterator<HistoryRecord> recs = history.getReader().findLast(NUMBER_OF_MSGS_IN_HISTORY); SimpleDateFormat sdf = new SimpleDateFormat(HistoryService.DATE_FORMAT); while (recs.hasNext()) { HistoryRecord hr = recs.next(); String contact = null; String recordProvider = null; Date timestamp = null; for (int i = 0; i < hr.getPropertyNames().length; i++) { String propName = hr.getPropertyNames()[i]; if (propName.equals(STRUCTURE_NAMES[0])) recordProvider = hr.getPropertyValues()[i]; else if (propName.equals(STRUCTURE_NAMES[1])) contact = hr.getPropertyValues()[i]; else if (propName.equals(STRUCTURE_NAMES[2])) { try { timestamp = sdf.parse(hr.getPropertyValues()[i]); } catch (ParseException e) { timestamp = new Date(Long.parseLong(hr.getPropertyValues()[i])); } } } if (recordProvider == null || contact == null) continue; if (after != null && timestamp != null && timestamp.before(after)) continue; if (recordProvider.equals(provider)) res.add(contact); } } } catch (IOException ex) { logger.error("cannot create recent_messages history", ex); } return res; } /** * Returns the cached recent messages history. * * @return * @throws IOException */ private History getHistory() throws IOException { synchronized (historyID) { HistoryService historyService = MessageHistoryActivator.getMessageHistoryService().getHistoryService(); if (history == null) { history = historyService.createHistory(historyID, recordStructure); // lets check the version if not our version, re-create // history (delete it) HistoryReader reader = history.getReader(); boolean delete = false; QueryResultSet<HistoryRecord> res = reader.findLast(1); if (res != null && res.hasNext()) { HistoryRecord hr = res.next(); if (hr.getPropertyValues().length >= 4) { if (!hr.getPropertyValues()[3].equals(RECENT_MSGS_VER)) delete = true; } else delete = true; } if (delete) { // delete it try { historyService.purgeLocallyStoredHistory(historyID); history = historyService.createHistory(historyID, recordStructure); } catch (IOException ex) { logger.error("Cannot delete recent_messages history", ex); } } } return history; } } /** * Returns the index of the source contact, in the list of recent messages. * * @param messageSourceContact * @return */ int getIndex(MessageSourceContact messageSourceContact) { synchronized (recentMessages) { for (int i = 0; i < recentMessages.size(); i++) if (recentMessages.get(i).equals(messageSourceContact)) return i; return -1; } } /** * Creates query for the given <tt>searchString</tt>. * * @param queryString the string to search for * @param contactCount the maximum count of result contacts * @return the created query */ @Override public ContactQuery createContactQuery(String queryString, int contactCount) { if (!StringUtils.isNullOrEmpty(queryString)) return null; recentQuery = new MessageSourceContactQuery(MessageSourceService.this); return recentQuery; } /** * Updates contact source contacts with status. * * @param evt the ContactPresenceStatusChangeEvent describing the status */ @Override public void contactPresenceStatusChanged(ContactPresenceStatusChangeEvent evt) { if (recentQuery == null) return; synchronized (recentMessages) { for (ComparableEvtObj msg : recentMessages) { if (msg.getContact() != null && msg.getContact().equals(evt.getSourceContact())) { recentQuery.updateContactStatus(msg, evt.getNewStatus()); } } } } @Override public void providerStatusChanged(ProviderPresenceStatusChangeEvent evt) { if (!evt.getNewStatus().isOnline() || evt.getOldStatus().isOnline()) return; handleProviderAdded(evt.getProvider(), true); } @Override public void providerStatusMessageChanged(PropertyChangeEvent evt) {} @Override public void localUserPresenceChanged(LocalUserChatRoomPresenceChangeEvent evt) { if (recentQuery == null) return; ComparableEvtObj srcContact = null; synchronized (recentMessages) { for (ComparableEvtObj msg : recentMessages) { if (msg.getRoom() != null && msg.getRoom().equals(evt.getChatRoom())) { srcContact = msg; break; } } } if (srcContact == null) return; String eventType = evt.getEventType(); if (LocalUserChatRoomPresenceChangeEvent.LOCAL_USER_JOINED.equals(eventType)) { recentQuery.updateContactStatus(srcContact, ChatRoomPresenceStatus.CHAT_ROOM_ONLINE); } else if ((LocalUserChatRoomPresenceChangeEvent.LOCAL_USER_LEFT.equals(eventType) || LocalUserChatRoomPresenceChangeEvent.LOCAL_USER_KICKED.equals(eventType) || LocalUserChatRoomPresenceChangeEvent.LOCAL_USER_DROPPED.equals(eventType))) { recentQuery.updateContactStatus(srcContact, ChatRoomPresenceStatus.CHAT_ROOM_OFFLINE); } } /** * Handles new events. * * @param obj the event object * @param provider the provider * @param id the id of the source of the event */ private void handle(EventObject obj, ProtocolProviderService provider, String id) { // check if provider - contact exist update message content synchronized (recentMessages) { ComparableEvtObj existingMsc = null; for (ComparableEvtObj msc : recentMessages) { if (msc.getProtocolProviderService().equals(provider) && msc.getContactAddress().equals(id)) { // update msc.update(obj); updateRecentMessageToHistory(msc); existingMsc = msc; } } if (existingMsc != null) { Collections.sort(recentMessages); oldestRecentMessage = recentMessages.get(recentMessages.size() - 1).getTimestamp(); if (recentQuery != null) { recentQuery.updateContact(existingMsc, existingMsc.getEventObject()); recentQuery.fireContactChanged(existingMsc); } return; } // if missing create source contact // and update recent messages, trim and sort MessageSourceContact newSourceContact = new MessageSourceContact(obj, MessageSourceService.this); newSourceContact.initDetails(obj); // we have already checked for duplicate ComparableEvtObj newMsg = new ComparableEvtObj(obj); recentMessages.add(newMsg); Collections.sort(recentMessages); oldestRecentMessage = recentMessages.get(recentMessages.size() - 1).getTimestamp(); // trim List<ComparableEvtObj> removedItems = null; if (recentMessages.size() > numberOfMessages) { removedItems = new ArrayList<ComparableEvtObj>( recentMessages.subList(numberOfMessages, recentMessages.size())); recentMessages.removeAll(removedItems); } // save saveRecentMessageToHistory(newMsg); // no query nothing to fire if (recentQuery == null) return; // now fire if (removedItems != null) { for (ComparableEvtObj msc : removedItems) { recentQuery.fireContactRemoved(msc); } } recentQuery.addQueryResult(newSourceContact); } } /** Adds recent message in history. */ private void saveRecentMessageToHistory(ComparableEvtObj msc) { synchronized (historyID) { // and create it try { History history = getHistory(); HistoryWriter writer = history.getWriter(); SimpleDateFormat sdf = new SimpleDateFormat(HistoryService.DATE_FORMAT); writer.addRecord( new String[] { msc.getProtocolProviderService().getAccountID().getAccountUniqueID(), msc.getContactAddress(), sdf.format(msc.getTimestamp()), RECENT_MSGS_VER }, NUMBER_OF_MSGS_IN_HISTORY); } catch (IOException ex) { logger.error("cannot create recent_messages history", ex); return; } } } /** Updates recent message in history. */ private void updateRecentMessageToHistory(final ComparableEvtObj msg) { synchronized (historyID) { // and create it try { History history = getHistory(); HistoryWriter writer = history.getWriter(); writer.updateRecord( new HistoryWriter.HistoryRecordUpdater() { HistoryRecord hr; @Override public void setHistoryRecord(HistoryRecord historyRecord) { this.hr = historyRecord; } @Override public boolean isMatching() { boolean providerFound = false; boolean contactFound = false; for (int i = 0; i < hr.getPropertyNames().length; i++) { String propName = hr.getPropertyNames()[i]; if (propName.equals(STRUCTURE_NAMES[0])) { if (msg.getProtocolProviderService() .getAccountID() .getAccountUniqueID() .equals(hr.getPropertyValues()[i])) { providerFound = true; } } else if (propName.equals(STRUCTURE_NAMES[1])) { if (msg.getContactAddress().equals(hr.getPropertyValues()[i])) { contactFound = true; } } } return contactFound && providerFound; } @Override public Map<String, String> getUpdateChanges() { HashMap<String, String> map = new HashMap<String, String>(); SimpleDateFormat sdf = new SimpleDateFormat(HistoryService.DATE_FORMAT); for (int i = 0; i < hr.getPropertyNames().length; i++) { String propName = hr.getPropertyNames()[i]; if (propName.equals(STRUCTURE_NAMES[0])) { map.put( propName, msg.getProtocolProviderService().getAccountID().getAccountUniqueID()); } else if (propName.equals(STRUCTURE_NAMES[1])) { map.put(propName, msg.getContactAddress()); } else if (propName.equals(STRUCTURE_NAMES[2])) { map.put(propName, sdf.format(msg.getTimestamp())); } else if (propName.equals(STRUCTURE_NAMES[3])) map.put(propName, RECENT_MSGS_VER); } return map; } }); } catch (IOException ex) { logger.error("cannot create recent_messages history", ex); return; } } } @Override public void messageReceived(MessageReceivedEvent evt) { if (isSMSEnabled && evt.getEventType() != MessageReceivedEvent.SMS_MESSAGE_RECEIVED) { return; } handle(evt, evt.getSourceContact().getProtocolProvider(), evt.getSourceContact().getAddress()); } @Override public void messageDelivered(MessageDeliveredEvent evt) { if (isSMSEnabled && !evt.isSmsMessage()) return; handle( evt, evt.getDestinationContact().getProtocolProvider(), evt.getDestinationContact().getAddress()); } /** * Not used. * * @param evt the <tt>MessageFailedEvent</tt> containing the ID of the */ @Override public void messageDeliveryFailed(MessageDeliveryFailedEvent evt) {} @Override public void messageReceived(ChatRoomMessageReceivedEvent evt) { if (isSMSEnabled) return; // ignore non conversation messages if (evt.getEventType() != ChatRoomMessageReceivedEvent.CONVERSATION_MESSAGE_RECEIVED) return; handle( evt, evt.getSourceChatRoom().getParentProvider(), evt.getSourceChatRoom().getIdentifier()); } @Override public void messageDelivered(ChatRoomMessageDeliveredEvent evt) { if (isSMSEnabled) return; handle( evt, evt.getSourceChatRoom().getParentProvider(), evt.getSourceChatRoom().getIdentifier()); } /** * Not used. * * @param evt the <tt>ChatroomMessageDeliveryFailedEvent</tt> containing */ @Override public void messageDeliveryFailed(ChatRoomMessageDeliveryFailedEvent evt) {} @Override public void messageReceived(AdHocChatRoomMessageReceivedEvent evt) { // TODO } @Override public void messageDelivered(AdHocChatRoomMessageDeliveredEvent evt) { // TODO } /** * Not used. * * @param evt the <tt>AdHocChatroomMessageDeliveryFailedEvent</tt> */ @Override public void messageDeliveryFailed(AdHocChatRoomMessageDeliveryFailedEvent evt) {} @Override public void subscriptionCreated(SubscriptionEvent evt) {} @Override public void subscriptionFailed(SubscriptionEvent evt) {} @Override public void subscriptionRemoved(SubscriptionEvent evt) {} @Override public void subscriptionMoved(SubscriptionMovedEvent evt) {} @Override public void subscriptionResolved(SubscriptionEvent evt) {} /** * If a contact is renamed update the locally stored message if any. * * @param evt the <tt>ContactPropertyChangeEvent</tt> containing the source */ @Override public void contactModified(ContactPropertyChangeEvent evt) { if (!evt.getPropertyName().equals(ContactPropertyChangeEvent.PROPERTY_DISPLAY_NAME)) return; Contact contact = evt.getSourceContact(); if (contact == null) return; for (ComparableEvtObj msc : recentMessages) { if (contact.equals(msc.getContact())) { if (recentQuery != null) recentQuery.updateContactDisplayName(msc, contact.getDisplayName()); return; } } } /** * Indicates that a MetaContact has been modified. * * @param evt the MetaContactListEvent containing the corresponding contact */ public void metaContactRenamed(MetaContactRenamedEvent evt) { for (ComparableEvtObj msc : recentMessages) { if (evt.getSourceMetaContact().containsContact(msc.getContact())) { if (recentQuery != null) recentQuery.updateContactDisplayName(msc, evt.getNewDisplayName()); } } } @Override public void supportedOperationSetsChanged(ContactCapabilitiesEvent event) { Contact contact = event.getSourceContact(); if (contact == null) return; for (ComparableEvtObj msc : recentMessages) { if (contact.equals(msc.getContact())) { if (recentQuery != null) recentQuery.updateCapabilities(msc, contact); return; } } } /** Permanently removes all locally stored message history, remove recent contacts. */ public void eraseLocallyStoredHistory() throws IOException { List<ComparableEvtObj> toRemove = null; synchronized (recentMessages) { toRemove = new ArrayList<ComparableEvtObj>(recentMessages); recentMessages.clear(); } if (recentQuery != null) { for (ComparableEvtObj msc : toRemove) { recentQuery.fireContactRemoved(msc); } } } /** * Permanently removes locally stored message history for the metacontact, remove any recent * contacts if any. */ public void eraseLocallyStoredHistory(MetaContact contact) throws IOException { List<ComparableEvtObj> toRemove = null; synchronized (recentMessages) { toRemove = new ArrayList<ComparableEvtObj>(); Iterator<Contact> iter = contact.getContacts(); while (iter.hasNext()) { Contact item = iter.next(); String id = item.getAddress(); ProtocolProviderService provider = item.getProtocolProvider(); for (ComparableEvtObj msc : recentMessages) { if (msc.getProtocolProviderService().equals(provider) && msc.getContactAddress().equals(id)) { toRemove.add(msc); } } } recentMessages.removeAll(toRemove); } if (recentQuery != null) { for (ComparableEvtObj msc : toRemove) { recentQuery.fireContactRemoved(msc); } } } /** * Permanently removes locally stored message history for the chatroom, remove any recent contacts * if any. */ public void eraseLocallyStoredHistory(ChatRoom room) { ComparableEvtObj toRemove = null; synchronized (recentMessages) { for (ComparableEvtObj msg : recentMessages) { if (msg.getRoom() != null && msg.getRoom().equals(room)) { toRemove = msg; break; } } if (toRemove == null) return; recentMessages.remove(toRemove); } if (recentQuery != null) recentQuery.fireContactRemoved(toRemove); } /** Object used to cache recent messages. */ private class ComparableEvtObj implements Comparable<ComparableEvtObj> { private EventObject eventObject; /** The protocol provider. */ private ProtocolProviderService ppService = null; /** The address. */ private String address = null; /** The timestamp. */ private Date timestamp = null; /** The contact instance. */ private Contact contact = null; /** The room instance. */ private ChatRoom room = null; /** * Constructs. * * @param source used to extract initial values. */ ComparableEvtObj(EventObject source) { update(source); } /** * Extract values from <tt>EventObject</tt>. * * @param source */ public void update(EventObject source) { this.eventObject = source; if (source instanceof MessageDeliveredEvent) { MessageDeliveredEvent e = (MessageDeliveredEvent) source; this.contact = e.getDestinationContact(); this.address = contact.getAddress(); this.ppService = contact.getProtocolProvider(); this.timestamp = e.getTimestamp(); } else if (source instanceof MessageReceivedEvent) { MessageReceivedEvent e = (MessageReceivedEvent) source; this.contact = e.getSourceContact(); this.address = contact.getAddress(); this.ppService = contact.getProtocolProvider(); this.timestamp = e.getTimestamp(); } else if (source instanceof ChatRoomMessageDeliveredEvent) { ChatRoomMessageDeliveredEvent e = (ChatRoomMessageDeliveredEvent) source; this.room = e.getSourceChatRoom(); this.address = room.getIdentifier(); this.ppService = room.getParentProvider(); this.timestamp = e.getTimestamp(); } else if (source instanceof ChatRoomMessageReceivedEvent) { ChatRoomMessageReceivedEvent e = (ChatRoomMessageReceivedEvent) source; this.room = e.getSourceChatRoom(); this.address = room.getIdentifier(); this.ppService = room.getParentProvider(); this.timestamp = e.getTimestamp(); } } @Override public String toString() { return "ComparableEvtObj{" + "address='" + address + '\'' + ", ppService=" + ppService + '}'; } /** * The timestamp of the message. * * @return the timestamp of the message. */ public Date getTimestamp() { return timestamp; } /** * The contact. * * @return the contact. */ public Contact getContact() { return contact; } /** * The room. * * @return the room. */ public ChatRoom getRoom() { return room; } /** * The protocol provider. * * @return the protocol provider. */ public ProtocolProviderService getProtocolProviderService() { return ppService; } /** * The address. * * @return the address. */ public String getContactAddress() { if (this.address != null) return this.address; return null; } /** * The event object. * * @return the event object. */ public EventObject getEventObject() { return eventObject; } /** * Compares two ComparableEvtObj. * * @param o the object to compare with * @return 0, less than zero, greater than zero, if equals, less or greater. */ @Override public int compareTo(ComparableEvtObj o) { if (o == null || o.getTimestamp() == null) return 1; return o.getTimestamp().compareTo(getTimestamp()); } /** * Checks if equals, and if this event object is used to create a MessageSourceContact, if the * supplied <tt>Object</tt> is instance of MessageSourceContact. * * @param o the object to check. * @return <tt>true</tt> if equals. */ @Override public boolean equals(Object o) { if (this == o) return true; if (o == null || (!(o instanceof MessageSourceContact) && getClass() != o.getClass())) return false; if (o instanceof ComparableEvtObj) { ComparableEvtObj that = (ComparableEvtObj) o; if (!address.equals(that.address)) return false; if (!ppService.equals(that.ppService)) return false; } else if (o instanceof MessageSourceContact) { MessageSourceContact that = (MessageSourceContact) o; if (!address.equals(that.getContactAddress())) return false; if (!ppService.equals(that.getProtocolProviderService())) return false; } else return false; return true; } @Override public int hashCode() { int result = address.hashCode(); result = 31 * result + ppService.hashCode(); return result; } } }
/** * The <tt>AndroidLoginRenderer</tt> is the Android renderer for login events. * * @author Yana Stamcheva * @author Pawel Domas */ public class AndroidLoginRenderer implements LoginRenderer { /** The logger */ private static final Logger logger = Logger.getLogger(AndroidLoginRenderer.class); /** The <tt>CallListener</tt>. */ private CallListener androidCallListener; /** The android implementation of the provider presence listener. */ private final ProviderPresenceStatusListener androidPresenceListener = new UIProviderPresenceStatusListener(); /** The security authority used by this login renderer. */ private final SecurityAuthority securityAuthority; /** Authorization handler instance. */ private final AuthorizationHandlerImpl authorizationHandler; /** Cached global status value */ private PresenceStatus globalStatus; /** List of global status listeners. */ private EventListenerList<PresenceStatus> globalStatusListeners = new EventListenerList<PresenceStatus>(); /** Caches avatar image to track the changes */ private byte[] localAvatarRaw; /** Local avatar drawable */ private Drawable localAvatar; /** Caches local status to track the changes */ private byte[] localStatusRaw; /** Local status drawable */ private Drawable localStatusDrawable; /** * Creates an instance of <tt>AndroidLoginRenderer</tt> by specifying the current * <tt>Context</tt>. * * @param defaultSecurityAuthority the security authority that will be used by this login renderer */ public AndroidLoginRenderer(SecurityAuthority defaultSecurityAuthority) { androidCallListener = new AndroidCallListener(); securityAuthority = defaultSecurityAuthority; authorizationHandler = new AuthorizationHandlerImpl(); } /** * Adds the user interface related to the given protocol provider. * * @param protocolProvider the protocol provider for which we add the user interface */ public void addProtocolProviderUI(ProtocolProviderService protocolProvider) { OperationSetBasicTelephony<?> telOpSet = protocolProvider.getOperationSet(OperationSetBasicTelephony.class); if (telOpSet != null) { telOpSet.addCallListener(androidCallListener); } OperationSetPresence presenceOpSet = protocolProvider.getOperationSet(OperationSetPresence.class); if (presenceOpSet != null) { presenceOpSet.addProviderPresenceStatusListener(androidPresenceListener); } } /** * Removes the user interface related to the given protocol provider. * * @param protocolProvider the protocol provider to remove */ public void removeProtocolProviderUI(ProtocolProviderService protocolProvider) { OperationSetBasicTelephony<?> telOpSet = protocolProvider.getOperationSet(OperationSetBasicTelephony.class); if (telOpSet != null) { telOpSet.removeCallListener(androidCallListener); } OperationSetPresence presenceOpSet = protocolProvider.getOperationSet(OperationSetPresence.class); if (presenceOpSet != null) { presenceOpSet.removeProviderPresenceStatusListener(androidPresenceListener); } // Removes all chat session for unregistered provider ChatSessionManager.removeAllChatsForProvider(protocolProvider); } /** * Starts the connecting user interface for the given protocol provider. * * @param protocolProvider the protocol provider for which we add the connecting user interface */ public void startConnectingUI(ProtocolProviderService protocolProvider) {} /** * Stops the connecting user interface for the given protocol provider. * * @param protocolProvider the protocol provider for which we remove the connecting user interface */ public void stopConnectingUI(ProtocolProviderService protocolProvider) {} /** * Indicates that the given protocol provider has been connected at the given time. * * @param protocolProvider the <tt>ProtocolProviderService</tt> corresponding to the connected * account * @param date the date/time at which the account has connected */ public void protocolProviderConnected(ProtocolProviderService protocolProvider, long date) { OperationSetPresence presence = AccountStatusUtils.getProtocolPresenceOpSet(protocolProvider); if (presence != null) { presence.setAuthorizationHandler(authorizationHandler); } updateGlobalStatus(); } /** * Indicates that a protocol provider connection has failed. * * @param protocolProvider the <tt>ProtocolProviderService</tt>, which connection failed * @param loginManagerCallback the <tt>LoginManager</tt> implementation, which is managing the * process */ public void protocolProviderConnectionFailed( final ProtocolProviderService protocolProvider, final LoginManager loginManagerCallback) { AccountID accountID = protocolProvider.getAccountID(); AndroidUtils.showAlertConfirmDialog( JitsiApplication.getGlobalContext(), JitsiApplication.getResString(R.string.service_gui_ERROR), JitsiApplication.getResString( R.string.service_gui_CONNECTION_FAILED_MSG, accountID.getUserID(), accountID.getService()), JitsiApplication.getResString(R.string.service_gui_RETRY), new DialogActivity.DialogListener() { public boolean onConfirmClicked(DialogActivity dialog) { loginManagerCallback.login(protocolProvider); return true; } public void onDialogCancelled(DialogActivity dialog) {} }); } /** * Returns the <tt>SecurityAuthority</tt> implementation related to this login renderer. * * @param protocolProvider the specific <tt>ProtocolProviderService</tt>, for which we're * obtaining a security authority * @return the <tt>SecurityAuthority</tt> implementation related to this login renderer */ public SecurityAuthority getSecurityAuthorityImpl(ProtocolProviderService protocolProvider) { return securityAuthority; } /** Updates Jitsi icon notification to reflect current global status. */ public void updateJitsiIconNotification() { String status; if (getGlobalStatus().isOnline()) { // At least one provider is online status = JitsiApplication.getResString(R.string.service_gui_ONLINE); } else { // There are no active providers so we consider to be in // the offline state status = JitsiApplication.getResString(R.string.service_gui_OFFLINE); } int notificationID = OSGiService.getGeneralNotificationId(); if (notificationID == -1) { logger.debug( "Not displaying status notification because" + " there's no global notification icon available."); return; } AndroidUtils.updateGeneralNotification( JitsiApplication.getGlobalContext(), notificationID, JitsiApplication.getResString(R.string.app_name), status, System.currentTimeMillis()); } /** * Adds global status listener. * * @param l the listener to be add. */ public void addGlobalStatusListener(EventListener<PresenceStatus> l) { globalStatusListeners.addEventListener(l); } /** * Removes global status listener. * * @param l the listener to remove. */ public void removeGlobalStatusListener(EventListener<PresenceStatus> l) { globalStatusListeners.removeEventListener(l); } /** * Returns current global status. * * @return current global status. */ public PresenceStatus getGlobalStatus() { if (globalStatus == null) { GlobalStatusService gss = AndroidGUIActivator.getGlobalStatusService(); globalStatus = gss != null ? gss.getGlobalPresenceStatus() : GlobalStatusEnum.OFFLINE; } return globalStatus; } /** AuthorizationHandler instance used by this login renderer. */ public AuthorizationHandlerImpl getAuthorizationHandler() { return authorizationHandler; } /** * Listens for all providerStatusChanged and providerStatusMessageChanged events in order to * refresh the account status panel, when a status is changed. */ private class UIProviderPresenceStatusListener implements ProviderPresenceStatusListener { public void providerStatusChanged(ProviderPresenceStatusChangeEvent evt) { updateGlobalStatus(); } public void providerStatusMessageChanged(PropertyChangeEvent evt) {} } /** * Indicates if the given <tt>protocolProvider</tt> related user interface is already rendered. * * @param protocolProvider the <tt>ProtocolProviderService</tt>, which related user interface * we're looking for * @return <tt>true</tt> if the given <tt>protocolProvider</tt> related user interface is already * rendered */ public boolean containsProtocolProviderUI(ProtocolProviderService protocolProvider) { return false; } /** Updates the global status by picking the most connected protocol provider status. */ private void updateGlobalStatus() { // Only if the GUI is active (bundle context will be null on shutdown) if (AndroidGUIActivator.bundleContext != null) { // Invalidate local status image localStatusRaw = null; // Invalidate global status globalStatus = null; globalStatusListeners.notifyEventListeners(getGlobalStatus()); } updateJitsiIconNotification(); } /** * Returns the local user avatar drawable. * * @return the local user avatar drawable. */ public Drawable getLocalAvatarDrawable() { GlobalDisplayDetailsService displayDetailsService = AndroidGUIActivator.getGlobalDisplayDetailsService(); byte[] avatarImage = displayDetailsService.getGlobalDisplayAvatar(); // Re-create drawable only if avatar has changed if (avatarImage != localAvatarRaw) { localAvatarRaw = avatarImage; localAvatar = AndroidImageUtil.roundedDrawableFromBytes(avatarImage); } return localAvatar; } /** * Returns the local user status drawable. * * @return the local user status drawable */ public synchronized Drawable getLocalStatusDrawable() { byte[] statusImage = StatusUtil.getContactStatusIcon(getGlobalStatus()); if (statusImage != localStatusRaw) { localStatusRaw = statusImage; localStatusDrawable = localStatusRaw != null ? AndroidImageUtil.drawableFromBytes(statusImage) : null; } return localStatusDrawable; } }
/** * Implements <tt>BundleActivator</tt> for the neomedia bundle. * * @author Martin Andre * @author Emil Ivov * @author Lyubomir Marinov * @author Boris Grozev */ public class NeomediaActivator implements BundleActivator { /** * The <tt>Logger</tt> used by the <tt>NeomediaActivator</tt> class and its instances for logging * output. */ private final Logger logger = Logger.getLogger(NeomediaActivator.class); /** Indicates if the audio configuration form should be disabled, i.e. not visible to the user. */ private static final String AUDIO_CONFIG_DISABLED_PROP = "net.java.sip.communicator.impl.neomedia.AUDIO_CONFIG_DISABLED"; /** Indicates if the video configuration form should be disabled, i.e. not visible to the user. */ private static final String VIDEO_CONFIG_DISABLED_PROP = "net.java.sip.communicator.impl.neomedia.VIDEO_CONFIG_DISABLED"; /** Indicates if the H.264 configuration form should be disabled, i.e. not visible to the user. */ private static final String H264_CONFIG_DISABLED_PROP = "net.java.sip.communicator.impl.neomedia.h264config.DISABLED"; /** Indicates if the ZRTP configuration form should be disabled, i.e. not visible to the user. */ private static final String ZRTP_CONFIG_DISABLED_PROP = "net.java.sip.communicator.impl.neomedia.zrtpconfig.DISABLED"; /** * Indicates if the call recording config form should be disabled, i.e. not visible to the user. */ private static final String CALL_RECORDING_CONFIG_DISABLED_PROP = "net.java.sip.communicator.impl.neomedia.callrecordingconfig.DISABLED"; /** * The name of the notification pop-up event displayed when the device configration has changed. */ private static final String DEVICE_CONFIGURATION_HAS_CHANGED = "DeviceConfigurationChanged"; /** * The context in which the one and only <tt>NeomediaActivator</tt> instance has started * executing. */ private static BundleContext bundleContext; /** * The <tt>ConfigurationService</tt> registered in {@link #bundleContext} and used by the * <tt>NeomediaActivator</tt> instance to read and write configuration properties. */ private static ConfigurationService configurationService; /** * The <tt>FileAccessService</tt> registered in {@link #bundleContext} and used by the * <tt>NeomediaActivator</tt> instance to safely access files. */ private static FileAccessService fileAccessService; /** The notifcation service to pop-up messages. */ private static NotificationService notificationService; /** * The one and only <tt>MediaServiceImpl</tt> instance registered in {@link #bundleContext} by the * <tt>NeomediaActivator</tt> instance. */ private static MediaServiceImpl mediaServiceImpl; /** * The <tt>ResourceManagementService</tt> registered in {@link #bundleContext} and representing * the resources such as internationalized and localized text and images used by the neomedia * bundle. */ private static ResourceManagementService resources; /** * The OSGi <tt>PacketLoggingService</tt> of {@link #mediaServiceImpl} in {@link #bundleContext} * and used for debugging. */ private static PacketLoggingService packetLoggingService = null; /** A listener to the click on the popup message concerning device configuration changes. */ private AudioDeviceConfigurationListener deviceConfigurationPropertyChangeListener; /** A {@link MediaConfigurationService} instance. */ // private static MediaConfigurationImpl mediaConfiguration; /** The audio configuration form used to define the capture/notify/playback audio devices. */ private static ConfigurationForm audioConfigurationForm; /** * Starts the execution of the neomedia bundle in the specified context. * * @param bundleContext the context in which the neomedia bundle is to start executing * @throws Exception if an error occurs while starting the execution of the neomedia bundle in the * specified context */ public void start(BundleContext bundleContext) throws Exception { if (logger.isDebugEnabled()) logger.debug("Started."); NeomediaActivator.bundleContext = bundleContext; // MediaService mediaServiceImpl = (MediaServiceImpl) LibJitsi.getMediaService(); bundleContext.registerService(MediaService.class.getName(), mediaServiceImpl, null); if (logger.isDebugEnabled()) logger.debug("Media Service ... [REGISTERED]"); // mediaConfiguration = new MediaConfigurationImpl(); // bundleContext.registerService( // MediaConfigurationService.class.getStatus(), // getMediaConfiguration(), // null); if (logger.isDebugEnabled()) logger.debug("Media Configuration ... [REGISTERED]"); ConfigurationService cfg = NeomediaActivator.getConfigurationService(); Dictionary<String, String> mediaProps = new Hashtable<String, String>(); mediaProps.put(ConfigurationForm.FORM_TYPE, ConfigurationForm.GENERAL_TYPE); // If the audio configuration form is disabled don't register it. // if ((cfg == null) || !cfg.getBoolean(AUDIO_CONFIG_DISABLED_PROP, false)) // { // audioConfigurationForm // = new LazyConfigurationForm( // AudioConfigurationPanel.class.getStatus(), // getClass().getClassLoader(), // "plugin.mediaconfig.AUDIO_ICON", // "impl.neomedia.configform.AUDIO", // 3); // // bundleContext.registerService( // ConfigurationForm.class.getStatus(), // audioConfigurationForm, // mediaProps); // // if (deviceConfigurationPropertyChangeListener == null) // { // // Initializes and registers the changed device configuration // // event ot the notification service. // getNotificationService(); // // deviceConfigurationPropertyChangeListener // = new AudioDeviceConfigurationListener(); // mediaServiceImpl // .getDeviceConfiguration() // .addPropertyChangeListener( // deviceConfigurationPropertyChangeListener); // } // } // If the video configuration form is disabled don't register it. // if ((cfg == null) || !cfg.getBoolean(VIDEO_CONFIG_DISABLED_PROP, false)) // { // bundleContext.registerService( // ConfigurationForm.class.getStatus(), // new LazyConfigurationForm( // VideoConfigurationPanel.class.getStatus(), // getClass().getClassLoader(), // "plugin.mediaconfig.VIDEO_ICON", // "impl.neomedia.configform.VIDEO", // 4), // mediaProps); // } // H.264 // If the H.264 configuration form is disabled don't register it. // if ((cfg == null) || !cfg.getBoolean(H264_CONFIG_DISABLED_PROP, false)) // { // Dictionary<String, String> h264Props // = new Hashtable<String, String>(); // // h264Props.put( // ConfigurationForm.FORM_TYPE, // ConfigurationForm.ADVANCED_TYPE); // bundleContext.registerService( // ConfigurationForm.class.getStatus(), // new LazyConfigurationForm( // ConfigurationPanel.class.getStatus(), // getClass().getClassLoader(), // "plugin.mediaconfig.VIDEO_ICON", // "impl.neomedia.configform.H264", // -1, // true), // h264Props); // } // ZRTP // If the ZRTP configuration form is disabled don't register it. // if ((cfg == null) || !cfg.getBoolean(ZRTP_CONFIG_DISABLED_PROP, false)) // { // Dictionary<String, String> securityProps // = new Hashtable<String, String>(); // // securityProps.put( ConfigurationForm.FORM_TYPE, // ConfigurationForm.SECURITY_TYPE); // bundleContext.registerService( // ConfigurationForm.class.getStatus(), // new LazyConfigurationForm( // SecurityConfigForm.class.getStatus(), // getClass().getClassLoader(), // "impl.media.security.zrtp.CONF_ICON", // "impl.media.security.zrtp.TITLE", // 0), // securityProps); // } // we use the nist-sdp stack to make parse sdp and we need to set the // following property to make sure that it would accept java generated // IPv6 addresses that contain address scope zones. System.setProperty("gov.nist.core.STRIP_ADDR_SCOPES", "true"); // AudioNotifierService AudioNotifierService audioNotifierService = LibJitsi.getAudioNotifierService(); audioNotifierService.setMute( (cfg == null) || !cfg.getBoolean("net.java.sip.communicator" + ".impl.sound.isSoundEnabled", true)); bundleContext.registerService(AudioNotifierService.class.getName(), audioNotifierService, null); if (logger.isInfoEnabled()) logger.info("Audio Notifier Service ...[REGISTERED]"); // Call Recording // If the call recording configuration form is disabled don't continue. // if ((cfg == null) // || !cfg.getBoolean(CALL_RECORDING_CONFIG_DISABLED_PROP, false)) // { // Dictionary<String, String> callRecordingProps // = new Hashtable<String, String>(); // // callRecordingProps.put( // ConfigurationForm.FORM_TYPE, // ConfigurationForm.ADVANCED_TYPE); // bundleContext.registerService( // ConfigurationForm.class.getStatus(), // new LazyConfigurationForm( // CallRecordingConfigForm.class.getStatus(), // getClass().getClassLoader(), // null, // "plugin.callrecordingconfig.CALL_RECORDING_CONFIG", // 1100, // true), // callRecordingProps); // } } /** * Stops the execution of the neomedia bundle in the specified context. * * @param bundleContext the context in which the neomedia bundle is to stop executing * @throws Exception if an error occurs while stopping the execution of the neomedia bundle in the * specified context */ public void stop(BundleContext bundleContext) throws Exception { try { if (deviceConfigurationPropertyChangeListener != null) { mediaServiceImpl .getDeviceConfiguration() .removePropertyChangeListener(deviceConfigurationPropertyChangeListener); if (deviceConfigurationPropertyChangeListener != null) { deviceConfigurationPropertyChangeListener.managePopupMessageListenerRegistration(false); deviceConfigurationPropertyChangeListener = null; } } } finally { configurationService = null; fileAccessService = null; mediaServiceImpl = null; resources = null; } } /** * Returns a reference to a ConfigurationService implementation currently registered in the bundle * context or null if no such implementation was found. * * @return a currently valid implementation of the ConfigurationService. */ public static ConfigurationService getConfigurationService() { if (configurationService == null) { configurationService = ServiceUtils.getService(bundleContext, ConfigurationService.class); } return configurationService; } /** * Returns a reference to a FileAccessService implementation currently registered in the bundle * context or null if no such implementation was found. * * @return a currently valid implementation of the FileAccessService . */ public static FileAccessService getFileAccessService() { if (fileAccessService == null) { fileAccessService = ServiceUtils.getService(bundleContext, FileAccessService.class); } return fileAccessService; } /** * Gets the <tt>MediaService</tt> implementation instance registered by the neomedia bundle. * * @return the <tt>MediaService</tt> implementation instance registered by the neomedia bundle */ public static MediaServiceImpl getMediaServiceImpl() { return mediaServiceImpl; } // public static MediaConfigurationService getMediaConfiguration() // { // return mediaConfiguration; // } /** * Gets the <tt>ResourceManagementService</tt> instance which represents the resources such as * internationalized and localized text and images used by the neomedia bundle. * * @return the <tt>ResourceManagementService</tt> instance which represents the resources such as * internationalized and localized text and images used by the neomedia bundle */ public static ResourceManagementService getResources() { if (resources == null) { resources = ResourceManagementServiceUtils.getService(bundleContext); } return resources; } /** * Returns a reference to the <tt>PacketLoggingService</tt> implementation currently registered in * the bundle context or null if no such implementation was found. * * @return a reference to a <tt>PacketLoggingService</tt> implementation currently registered in * the bundle context or null if no such implementation was found. */ public static PacketLoggingService getPacketLogging() { if (packetLoggingService == null) { packetLoggingService = ServiceUtils.getService(bundleContext, PacketLoggingService.class); } return packetLoggingService; } /** * Returns the <tt>NotificationService</tt> obtained from the bundle context. * * @return The <tt>NotificationService</tt> obtained from the bundle context. */ public static NotificationService getNotificationService() { if (notificationService == null) { // Get the notification service implementation ServiceReference notifReference = bundleContext.getServiceReference(NotificationService.class.getName()); notificationService = (NotificationService) bundleContext.getService(notifReference); if (notificationService != null) { // Register a popup message for a device configuration changed // notification. notificationService.registerDefaultNotificationForEvent( DEVICE_CONFIGURATION_HAS_CHANGED, net.java.sip.communicator.service.notification.NotificationAction.ACTION_POPUP_MESSAGE, "Device onfiguration has changed", null); } } return notificationService; } /** A listener to the click on the popup message concerning device configuration changes. */ private class AudioDeviceConfigurationListener implements PropertyChangeListener /*, SystrayPopupMessageListener*/ { /** * A boolean used to verify that this listener registers only once to the popup message * notification handler. */ private boolean isRegisteredToPopupMessageListener = false; /** * Registers or unregister as a popup message listener to detect when a user click on * notification saying that the device configuration has changed. * * @param enable True to register to the popup message notifcation handler. False to unregister. */ public void managePopupMessageListenerRegistration(boolean enable) { Iterator<NotificationHandler> notificationHandlers = notificationService .getActionHandlers( net.java.sip.communicator.service.notification.NotificationAction .ACTION_POPUP_MESSAGE) .iterator(); NotificationHandler notificationHandler; while (notificationHandlers.hasNext()) { notificationHandler = notificationHandlers.next(); if (notificationHandler instanceof PopupMessageNotificationHandler) { // Register. if (enable) { // ((PopupMessageNotificationHandler) notificationHandler) // .addPopupMessageListener(this); } // Unregister. else { // ((PopupMessageNotificationHandler) notificationHandler) // .removePopupMessageListener(this); } } } } /** * Function called when an audio device is plugged or unplugged. * * @param event The property change event which may concern the audio device. */ public void propertyChange(PropertyChangeEvent event) { if (DeviceConfiguration.PROP_AUDIO_SYSTEM_DEVICES.equals(event.getPropertyName())) { NotificationService notificationService = getNotificationService(); if (notificationService != null) { // Registers only once to the popup message notification // handler. if (!isRegisteredToPopupMessageListener) { isRegisteredToPopupMessageListener = true; managePopupMessageListenerRegistration(true); } // Fires the popup notification. ResourceManagementService resources = NeomediaActivator.getResources(); Map<String, Object> extras = new HashMap<String, Object>(); extras.put(NotificationData.POPUP_MESSAGE_HANDLER_TAG_EXTRA, this); notificationService.fireNotification( DEVICE_CONFIGURATION_HAS_CHANGED, resources.getI18NString("impl.media.configform" + ".AUDIO_DEVICE_CONFIG_CHANGED"), resources.getI18NString( "impl.media.configform" + ".AUDIO_DEVICE_CONFIG_MANAGMENT_CLICK"), null, extras); } } } /** * Indicates that user has clicked on the systray popup message. * * @param evt the event triggered when user clicks on the systray popup message */ // public void popupMessageClicked(SystrayPopupMessageEvent evt) // { // // Checks if this event is fired from one click on one of our popup // // message. // if(evt.getTag() == deviceConfigurationPropertyChangeListener) // { // // Get the UI service // ServiceReference uiReference = bundleContext // .getServiceReference(UIService.class.getStatus()); // // UIService uiService = (UIService) bundleContext // .getService(uiReference); // // if(uiService != null) // { // // Shows the audio configuration window. // ConfigurationContainer configurationContainer // = uiService.getConfigurationContainer(); // configurationContainer.setSelected(audioConfigurationForm); // configurationContainer.setVisible(true); // } // } // } } public static BundleContext getBundleContext() { return bundleContext; } }