diff --git a/resources/config/defaults.properties b/resources/config/defaults.properties index 6b1f8eb20..c7a7650f4 100644 --- a/resources/config/defaults.properties +++ b/resources/config/defaults.properties @@ -73,6 +73,7 @@ plugin.ippiaccregwizz.REGISTER_LINK=https://soap.ippi.fr/subscription/jitsi.php? # Global shortcuts defaults values impl.keybinding.global.answer.1=shift ctrl pressed A impl.keybinding.global.hangup.1=shift ctrl pressed H +impl.keybinding.global.answer_hangup.1=shift ctrl pressed P impl.keybinding.global.contactlist.1=shift ctrl pressed L impl.keybinding.global.mute.1=shift ctrl pressed M @@ -92,4 +93,4 @@ net.java.sip.communicator.service.neomedia.SDES_CIPHER_SUITES=AES_CM_128_HMAC_SH impl.gui.PARANOIA_UI=false -impl.gui.I_DONT_CARE_THAT_MUCH_ABOUT_SECURITY=false \ No newline at end of file +impl.gui.I_DONT_CARE_THAT_MUCH_ABOUT_SECURITY=false diff --git a/resources/languages/resources.properties b/resources/languages/resources.properties index 35391019d..0368d8143 100644 --- a/resources/languages/resources.properties +++ b/resources/languages/resources.properties @@ -1166,6 +1166,7 @@ plugin.keybindings.OPEN_HISTORY=Show History plugin.keybindings.OPEN_SMILIES=Show Smileys plugin.keybindings.globalchooser.ANSWER_CALL=Answer call plugin.keybindings.globalchooser.HANGUP_CALL=Hangup call +plugin.keybindings.globalchooser.ANSWER_HANGUP_CALL=Answer/Hangup call plugin.keybindings.globalchooser.SHOW_CONTACTLIST=Show contact list plugin.keybindings.globalchooser.MUTE_CALLS=Mute calls plugin.keybindings.globalchooser.SHORTCUT_NAME=Name diff --git a/src/net/java/sip/communicator/impl/globalshortcut/CallShortcut.java b/src/net/java/sip/communicator/impl/globalshortcut/CallShortcut.java index 8b3725e92..fe3ceb0e6 100644 --- a/src/net/java/sip/communicator/impl/globalshortcut/CallShortcut.java +++ b/src/net/java/sip/communicator/impl/globalshortcut/CallShortcut.java @@ -21,6 +21,7 @@ * Shortcut for call (take the call, hang up, ...). * * @author Sebastien Vincent + * @author Vincent Lucas */ public class CallShortcut implements GlobalShortcutListener, @@ -32,6 +33,17 @@ public class CallShortcut */ private static final Logger logger = Logger.getLogger(CallShortcut.class); + /** + * Lists the call actions available: ANSWER or HANGUP. + */ + private enum CallAction + { + // Answers an incoming call. + ANSWER, + // Hangs up a call. + HANGUP + } + /** * Keybindings service. */ @@ -39,14 +51,14 @@ public class CallShortcut GlobalShortcutActivator.getKeybindingsService(); /** - * List of incoming calls. + * List of incoming calls not yet answered. */ private ArrayList incomingCalls = new ArrayList(); /** - * List of outgoing calls. + * List of answered calls: active (off hold) or on hold. */ - private ArrayList outgoingCalls = new ArrayList(); + private ArrayList answeredCalls = new ArrayList(); /** * Next mute state action. @@ -83,158 +95,47 @@ public void shortcutReceived(GlobalShortcutEvent evt) keystroke.getKeyCode() == ks.getKeyCode() && keystroke.getModifiers() == ks.getModifiers()) { - synchronized(incomingCalls) - { - int size = incomingCalls.size(); - - for(int i = 0 ; i < size ; i++) - { - Call c = incomingCalls.get(i); - - if(c.getCallPeers().next().getState() == - CallPeerState.INCOMING_CALL) - { - choosenCall = c; - break; - } - } - } + // Try to answer the new incoming call, if there is any. + manageNextIncomingCall(CallAction.ANSWER); - if(choosenCall == null) - return; - - final OperationSetBasicTelephony opSet = - choosenCall.getProtocolProvider().getOperationSet( - OperationSetBasicTelephony.class); - final Call cCall = choosenCall; - - new Thread() - { - public void run() - { - try - { - opSet.answerCallPeer( - cCall.getCallPeers().next()); - } - catch(OperationFailedException e) - { - logger.info( - "Failed to answer call via global shortcut", - e); - } - } - }.start(); } else if(entry.getKey().equals("hangup") && keystroke.getKeyCode() == ks.getKeyCode() && keystroke.getModifiers() == ks.getModifiers()) { - Call incomingCall = null; - Call outgoingCall = null; - - synchronized(incomingCalls) + // Try to hang up the new incoming call. + if(!manageNextIncomingCall(CallAction.HANGUP)) { - int size = incomingCalls.size(); - - for(int i = 0 ; i < size ; i++) + // There was no new incoming call. + // Thus, we try to close all active calls. + if(!closeAnsweredCalls(true)) { - Call c = incomingCalls.get(i); - - if(c.getCallPeers().next().getState() == - CallPeerState.INCOMING_CALL && - incomingCall == null) - { - incomingCall = c; - break; - } - else if(c.getCallPeers().next().getState() == - CallPeerState.CONNECTED) - { - choosenCall = c; - break; - } + // There was no active call. + // Thus, we close all answered (inactive, hold on) + // calls. + closeAnsweredCalls(false); } } - synchronized(outgoingCalls) + } + else if(entry.getKey().equals("answer_hangup") && + keystroke.getKeyCode() == ks.getKeyCode() && + keystroke.getModifiers() == ks.getModifiers()) + { + // Try to answer the new incoming call. + if(!manageNextIncomingCall(CallAction.ANSWER)) { - int size = outgoingCalls.size(); - - for(int i = 0 ; i < size ; i++) + // There was no new incoming call. + // Thus, we try to close all active calls. + if(!closeAnsweredCalls(true)) { - Call c = outgoingCalls.get(i); - - if((c.getCallPeers().next().getState() == - CallPeerState.CONNECTING || - c.getCallPeers().next().getState() == - CallPeerState.ALERTING_REMOTE_SIDE) && - outgoingCall == null) - { - outgoingCall = c; - break; - } - else if(c.getCallPeers().next().getState() == - CallPeerState.CONNECTED) - { - choosenCall = c; - break; - } - + // There was no active call. + // Thus, we close all answered (inactive, hold on) + // calls. + closeAnsweredCalls(false); } } - if(choosenCall == null && incomingCall != null) - { - // maybe we just want to hangup (refuse) incoming call - choosenCall = incomingCall; - } - if(choosenCall == null && outgoingCall != null) - { - // maybe we just want to hangup (cancel) outgoing call - choosenCall = outgoingCall; - } - - if(choosenCall == null) - return; - - final OperationSetBasicTelephony opSet = - choosenCall.getProtocolProvider().getOperationSet( - OperationSetBasicTelephony.class); - - final Call cCall = choosenCall; - new Thread() - { - public void run() - { - try - { - Iterator peers = - cCall.getCrossProtocolCallPeers(); - - while(peers.hasNext()) - { - CallPeer peer = peers.next(); - - peer.getProtocolProvider().getOperationSet( - OperationSetBasicTelephony.class). - hangupCallPeer(peer); - } - - peers = cCall.getCallPeers(); - - while(peers.hasNext()) - opSet.hangupCallPeer( - peers.next()); - } - catch(OperationFailedException e) - { - logger.info( - "Failed to answer call via global shortcut", - e); - } - } - }.start(); } else if(entry.getKey().equals("mute") && keystroke.getKeyCode() == ks.getKeyCode() && @@ -248,9 +149,9 @@ else if(entry.getKey().equals("mute") && } } - synchronized(outgoingCalls) + synchronized(answeredCalls) { - for(Call c : outgoingCalls) + for(Call c : answeredCalls) { handleMute(c); } @@ -263,33 +164,74 @@ else if(entry.getKey().equals("mute") && } } + /** + * This method is called by a protocol provider whenever an incoming call is + * received. + * + * @param event a CallEvent instance describing the new incoming call + */ public void incomingCallReceived(CallEvent event) { - Call sourceCall = event.getSourceCall(); - - synchronized(incomingCalls) - { - incomingCalls.add(sourceCall); - } + CallShortcut.addCall(event.getSourceCall(), this.incomingCalls); } + /** + * This method is called by a protocol provider upon initiation of an + * outgoing call. + *

+ * + * @param event a CalldEvent instance describing the new incoming call. + */ public void outgoingCallCreated(CallEvent event) { - synchronized(outgoingCalls) + CallShortcut.addCall(event.getSourceCall(), this.answeredCalls); + } + + /** + * Adds a created call to the managed call list. + * + * @param call The call to add to the managed call list. + * @param calls The managed call list. + */ + private static void addCall(Call call, ArrayList calls) + { + synchronized(calls) { - if(event.getSourceCall().getCallGroup() == null) - outgoingCalls.add(event.getSourceCall()); + if(call.getCallGroup() == null) + calls.add(call); } } + /** + * Indicates that all peers have left the source call and that it has + * been ended. The event may be considered redundant since there are already + * events issued upon termination of a single call peer but we've + * decided to keep it for listeners that are only interested in call + * duration and don't want to follow other call details. + * + * @param event the CallEvent containing the source call. + */ public void callEnded(CallEvent event) { Call sourceCall = event.getSourceCall(); - if(incomingCalls.contains(sourceCall)) - incomingCalls.remove(sourceCall); - else if(outgoingCalls.contains(sourceCall)) - outgoingCalls.remove(sourceCall); + CallShortcut.removeCall(sourceCall, incomingCalls); + CallShortcut.removeCall(sourceCall, answeredCalls); + } + + /** + * Removes an ended call to the managed call list. + * + * @param call The call to remove from the managed call list. + * @param calls The managed call list. + */ + private static void removeCall(Call call, ArrayList calls) + { + synchronized(calls) + { + if(calls.contains(call)) + calls.remove(call); + } } /** @@ -304,14 +246,218 @@ private void handleMute(Call c) return; // handle only connected peer (no on hold peer) - if(c.getCallPeers().next().getState() != - CallPeerState.CONNECTED) + if(c.getCallPeers().next().getState() != CallPeerState.CONNECTED) return; MediaAwareCall cc = (MediaAwareCall)c; - if(mute && !cc.isMute()) - cc.setMute(true); - else if(!mute && cc.isMute()) - cc.setMute(false); + if(mute != cc.isMute()) + { + cc.setMute(mute); + } + } + + /** + * Answers or puts on/off hold the given call. + * + * @param call The call to answer, to put on hold, or to put off hold. + * @param callAction The action (ANSWER or HANGUP) to do. + */ + private static void doCallAction( + final Call call, + final CallAction callAction) + { + new Thread() + { + public void run() + { + try + { + List calls; + CallGroup group = call.getCallGroup(); + if(group != null) + { + calls = group.getCalls(); + } + else + { + calls = new Vector(); + calls.add(call); + } + Call tmpCall; + Iterator callPeers; + CallPeer callPeer; + + for(int i = 0; i < calls.size(); ++i) + { + tmpCall = calls.get(i); + final OperationSetBasicTelephony opSet = + tmpCall.getProtocolProvider() + .getOperationSet(OperationSetBasicTelephony.class); + callPeers = tmpCall.getCallPeers(); + while(callPeers.hasNext()) + { + callPeer = callPeers.next(); + switch(callAction) + { + case ANSWER: + if(callPeer.getState() == + CallPeerState.INCOMING_CALL) + { + opSet.answerCallPeer(callPeer); + } + break; + case HANGUP: + opSet.hangupCallPeer(callPeer); + break; + } + } + } + + } + catch(OperationFailedException e) + { + logger.info( + "Failed to answer/hangup call via global shortcut", + e); + } + } + }.start(); + + + } + + /** + * Answers or hangs up the next incoming call if any. + * + * @param callAction The action (ANSWER or HANGUP) to do. + * + * @return True if the next incoming call has been answered/hanged up. False + * if there is no incoming call remaining. + */ + private boolean manageNextIncomingCall(CallAction callAction) + { + synchronized(incomingCalls) + { + Call call; + int i = incomingCalls.size(); + while(i != 0) + { + --i; + call = incomingCalls.get(i); + + // Either this incoming call is already answered, or we will + // answered it. Thus, we switch it to the answered list. + answeredCalls.add(call); + incomingCalls.remove(i); + + // We find a call not answered yet. + if(call.getCallState() == CallState.CALL_INITIALIZATION) + { + // Answer or hang up the ringing call. + CallShortcut.doCallAction(call, callAction); + return true; + } + } + } + return false; + } + + /** + * Closes only active calls, or all answered calls depending on the + * closeOnlyActiveCalls parameter. + * + * @param closeOnlyActiveCalls Boolean which must be set to true to only + * removes the active calls. Otherwise, the whole answered calls will be + * closed. + * + * @return True if there was at least one call closed. False otherwise. + */ + private boolean closeAnsweredCalls(boolean closeOnlyActiveCalls) + { + boolean isAtLeastOneCallClosed = false; + Call call; + + synchronized(answeredCalls) + { + int i = answeredCalls.size(); + while(i != 0) + { + --i; + call = answeredCalls.get(i); + + // If we are not limited to active call, then we close all + // answered calls. Otherwise, we close only active calls (hold + // off calls). + if(!closeOnlyActiveCalls + || CallShortcut.isActiveCall(call)) + { + CallShortcut.doCallAction(call, CallAction.HANGUP); + answeredCalls.remove(i); + isAtLeastOneCallClosed = true; + } + } + } + + return isAtLeastOneCallClosed; + } + + /** + * Return true if the call is active: at least one call peer is active (not + * on hold). + * + * @param call The call that this function will determine as active or not. + * + * @return True if the call is active. False, otherwise. + */ + private static boolean isActiveCall(Call call) + { + List calls; + CallGroup group = call.getCallGroup(); + if(group != null) + { + calls = group.getCalls(); + } + else + { + calls = new Vector(); + calls.add(call); + } + + for(int i = 0; i < calls.size(); ++i) + { + if(isAtLeastOneActiveCallPeer(calls.get(i).getCallPeers())) + { + // If there is a single active call peer, then the call is + // active. + return true; + } + } + return false; + } + + /** + * Return true if at least one call peer is active: not on hold. + * + * @param callPeers The call peer list which may contain at least an active + * call. + + * @return True if at least one call peer is active: not on hold. False, + * otherwise. + */ + private static boolean isAtLeastOneActiveCallPeer( + Iterator callPeers) + { + CallPeer callPeer; + + while(callPeers.hasNext()) + { + callPeer = callPeers.next(); + if(!CallPeerState.isOnHold(callPeer.getState())) + { + // If at least one peer is active, then the call is active. + return true; + } + } + return false; } } diff --git a/src/net/java/sip/communicator/impl/globalshortcut/GlobalShortcutServiceImpl.java b/src/net/java/sip/communicator/impl/globalshortcut/GlobalShortcutServiceImpl.java index 9268dc3e5..bb183b8d7 100644 --- a/src/net/java/sip/communicator/impl/globalshortcut/GlobalShortcutServiceImpl.java +++ b/src/net/java/sip/communicator/impl/globalshortcut/GlobalShortcutServiceImpl.java @@ -374,6 +374,7 @@ public synchronized void reloadGlobalShortcuts() { if(entry.getKey().equals("answer") || entry.getKey().equals("hangup") || + entry.getKey().equals("answer_hangup") || entry.getKey().equals("mute")) { for(AWTKeyStroke e : entry.getValue()) diff --git a/src/net/java/sip/communicator/impl/keybindings/KeybindingsServiceImpl.java b/src/net/java/sip/communicator/impl/keybindings/KeybindingsServiceImpl.java index 108905116..35efb39ff 100644 --- a/src/net/java/sip/communicator/impl/keybindings/KeybindingsServiceImpl.java +++ b/src/net/java/sip/communicator/impl/keybindings/KeybindingsServiceImpl.java @@ -336,8 +336,8 @@ public Map> getGlobalShortcutFromConfiguration() String shortcut2 = null; String propName = null; String propName2 = null; - String names[] = new String[]{"answer", "hangup", "contactlist", - "mute"}; + String names[] = new String[]{"answer", "hangup", "answer_hangup", + "contactlist", "mute"}; Object configured = configService.getProperty( "net.java.sip.communicator.impl.keybinding.global.configured"); diff --git a/src/net/java/sip/communicator/plugin/keybindingchooser/globalchooser/GlobalShortcutConfigForm.java b/src/net/java/sip/communicator/plugin/keybindingchooser/globalchooser/GlobalShortcutConfigForm.java index 034d6d8c4..d916f5a95 100644 --- a/src/net/java/sip/communicator/plugin/keybindingchooser/globalchooser/GlobalShortcutConfigForm.java +++ b/src/net/java/sip/communicator/plugin/keybindingchooser/globalchooser/GlobalShortcutConfigForm.java @@ -197,6 +197,11 @@ else if(key.equals("hangup")) desc = Resources.getString( "plugin.keybindings.globalchooser.HANGUP_CALL"); } + else if(key.equals("answer_hangup")) + { + desc = Resources.getString( + "plugin.keybindings.globalchooser.ANSWER_HANGUP_CALL"); + } else if(key.equals("contactlist")) { desc = Resources.getString( @@ -247,6 +252,11 @@ else if(entry.getAction().equals(Resources.getString( { desc = "hangup"; } + else if(entry.getAction().equals(Resources.getString( + "plugin.keybindings.globalchooser.ANSWER_HANGUP_CALL"))) + { + desc = "answer_hangup"; + } else if(entry.getAction().equals(Resources.getString( "plugin.keybindings.globalchooser.SHOW_CONTACTLIST"))) {