diff options
author | Vincent Lucas <chenzo@jitsi.org> | 2012-05-15 18:14:48 +0000 |
---|---|---|
committer | Vincent Lucas <chenzo@jitsi.org> | 2012-05-15 18:14:48 +0000 |
commit | eae129a72c7f72279112e6098f563b7d90ceaf93 (patch) | |
tree | 6d5e75f6b27849100aa6c99cb747d6543f9904d2 | |
parent | 9e2ff448e7898ab81105b213b8dbc74ca7da8600 (diff) | |
download | jitsi-eae129a72c7f72279112e6098f563b7d90ceaf93.zip jitsi-eae129a72c7f72279112e6098f563b7d90ceaf93.tar.gz jitsi-eae129a72c7f72279112e6098f563b7d90ceaf93.tar.bz2 |
Adds an "answer/hangup" call global key shortcut. Modifies the "hangup" global
key shortcut behavior.
The answer/hangup shortcut:
--------------------------
This shortcut may be helpful for headsets with a single button for answering or
hanging up a call.
By default this shortcut is set to "Ctrl + Shift + p". When pressed:
1) If there is at least an incoming call, then Jitsi answers the first incoming
call.
2) If there is no incoming call, then Jitsi closes all active calls (An active
call is a call with at least one peer which is not on hold).
3) If there is no incoming call and no active call, then Jitsi closes all
remaining calls (which are all on hold).
New hangup shortcut behavior:
-----------------------------
The hangup global key shortcut behavior is modified as follow:
1) If there is at least an incoming call, then Jitsi closes the first incoming
call.
2) If there is no incoming call, then Jitsi closes all active calls (An active
call is a call with at least one peer which is not on hold).
3) If there is no incoming call and no active call, then Jitsi closes all
remaining calls (which are all on hold).
6 files changed, 322 insertions, 163 deletions
diff --git a/resources/config/defaults.properties b/resources/config/defaults.properties index 6b1f8eb..c7a7650 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 3539101..0368d81 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 8b3725e..fe3ceb0 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 @@ import net.java.sip.communicator.util.*; * Shortcut for call (take the call, hang up, ...). * * @author Sebastien Vincent + * @author Vincent Lucas */ public class CallShortcut implements GlobalShortcutListener, @@ -33,20 +34,31 @@ 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. */ private KeybindingsService keybindingsService = GlobalShortcutActivator.getKeybindingsService(); /** - * List of incoming calls. + * List of incoming calls not yet answered. */ private ArrayList<Call> incomingCalls = new ArrayList<Call>(); /** - * List of outgoing calls. + * List of answered calls: active (off hold) or on hold. */ - private ArrayList<Call> outgoingCalls = new ArrayList<Call>(); + private ArrayList<Call> answeredCalls = new ArrayList<Call>(); /** * Next mute state action. @@ -83,158 +95,47 @@ public class CallShortcut 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<? extends CallPeer> 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 @@ public class CallShortcut } } - synchronized(outgoingCalls) + synchronized(answeredCalls) { - for(Call c : outgoingCalls) + for(Call c : answeredCalls) { handleMute(c); } @@ -263,33 +164,74 @@ public class CallShortcut } } + /** + * 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. + * <p> + * + * @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<Call> 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 <tt>CallEvent</tt> 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<Call> calls) + { + synchronized(calls) + { + if(calls.contains(call)) + calls.remove(call); + } } /** @@ -304,14 +246,218 @@ public class CallShortcut 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<Call> calls; + CallGroup group = call.getCallGroup(); + if(group != null) + { + calls = group.getCalls(); + } + else + { + calls = new Vector<Call>(); + calls.add(call); + } + Call tmpCall; + Iterator<? extends CallPeer> 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<Call> calls; + CallGroup group = call.getCallGroup(); + if(group != null) + { + calls = group.getCalls(); + } + else + { + calls = new Vector<Call>(); + 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<? extends CallPeer> 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 9268dc3..bb183b8 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 class GlobalShortcutServiceImpl { 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 1089051..35efb39 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 @@ class KeybindingsServiceImpl 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 034d6d8..d916f5a 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 @@ public class GlobalShortcutConfigForm 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( @@ -248,6 +253,11 @@ public class GlobalShortcutConfigForm 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"))) { desc = "contactlist"; |