aboutsummaryrefslogtreecommitdiffstats
path: root/src/net/java/sip/communicator/service/protocol/SingleCallInProgressPolicy.java
blob: 9512fce488135da54b2c55a70fcde57708ca5282 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
/*
 * Jitsi, the OpenSource Java VoIP and Instant Messaging client.
 *
 * Distributable under LGPL license.
 * See terms of license at gnu.org.
 */
package net.java.sip.communicator.service.protocol;

import java.util.*;

import org.osgi.framework.*;

import net.java.sip.communicator.service.protocol.event.*;
import net.java.sip.communicator.util.*;

/**
 * Imposes the policy to have one call in progress i.e. to put existing calls on
 * hold when a new call enters in progress.
 *
 * @author Lubomir Marinov
 */
public class SingleCallInProgressPolicy
{

    /**
     * The name of the configuration property which specifies whether
     * <code>SingleCallInProgressPolicy</code> is enabled i.e. whether it should
     * put existing calls on hold when a new call enters in progress.
     */
    private static final String PNAME_SINGLE_CALL_IN_PROGRESS_POLICY_ENABLED
        = "net.java.sip.communicator.impl.protocol.SingleCallInProgressPolicy.enabled";

    /**
     * Implements the listeners interfaces used by this policy.
     */
    private class SingleCallInProgressPolicyListener
        implements CallChangeListener,
                   CallListener,
                   ServiceListener
    {
        /**
         * Stops tracking the state of a specific <code>Call</code> and no
         * longer tries to put it on hold when it ends.
         *
         * @see CallListener#callEnded(CallEvent)
         */
        public void callEnded(CallEvent callEvent)
        {
            SingleCallInProgressPolicy.this.handleCallEvent(
                CallEvent.CALL_ENDED, callEvent);
        }

        /**
         * Does nothing because adding <code>CallPeer<code>s to
         * <code>Call</code>s isn't related to the policy to put existing calls
         * on hold when a new call becomes in-progress and just implements
         * <code>CallChangeListener</code>.
         *
         * @see CallChangeListener#callPeerAdded(CallPeerEvent)
         */
        public void callPeerAdded( CallPeerEvent callPeerEvent)
        {

            /*
             * Not of interest, just implementing CallChangeListener in which
             * only #callStateChanged(CallChangeEvent) is of interest.
             */
        }

        /**
         * Does nothing because removing <code>CallPeer<code>s to
         * <code>Call</code>s isn't related to the policy to put existing calls
         * on hold when a new call becomes in-progress and just implements
         * <code>CallChangeListener</code>.
         *
         * @see CallChangeListener#callPeerRemoved(CallPeerEvent)
         */
        public void callPeerRemoved( CallPeerEvent callPeerEvent)
        {

            /*
             * Not of interest, just implementing CallChangeListener in which
             * only #callStateChanged(CallChangeEvent) is of interest.
             */
        }

        /**
         * Upon a <code>Call</code> changing its state to
         * <code>CallState.CALL_IN_PROGRESS</code>, puts the other existing
         * <code>Call</code>s on hold.
         *
         * @param callChangeEvent the <tt>CallChangeEvent</tt> that we are to
         * deliver.
         *
         * @see CallChangeListener#callStateChanged(CallChangeEvent)
         */
        public void callStateChanged(CallChangeEvent callChangeEvent)
        {
            // we are interested only in CALL_STATE_CHANGEs
            if(!callChangeEvent.getEventType().equals(CallChangeEvent.CALL_STATE_CHANGE))
                return;

            SingleCallInProgressPolicy.this.callStateChanged(callChangeEvent);
        }

        /**
         * Remembers an incoming <code>Call</code> so that it can put the other
         * existing <code>Call</code>s on hold when it changes its state to
         * <code>CallState.CALL_IN_PROGRESS</code>.
         *
         * @see CallListener#incomingCallReceived(CallEvent)
         */
        public void incomingCallReceived(CallEvent callEvent)
        {
            SingleCallInProgressPolicy.this.handleCallEvent(
                CallEvent.CALL_RECEIVED, callEvent);
        }

        /**
         * Remembers an outgoing <code>Call</code> so that it can put the other
         * existing <code>Call</code>s on hold when it changes its state to
         * <code>CallState.CALL_IN_PROGRESS</code>.
         *
         * @see CallListener#outgoingCallCreated(CallEvent)
         */
        public void outgoingCallCreated(CallEvent callEvent)
        {
            SingleCallInProgressPolicy.this.handleCallEvent(
                CallEvent.CALL_INITIATED, callEvent);
        }

        /**
         * Starts/stops tracking the new <code>Call</code>s originating from a
         * specific <code>ProtocolProviderService</code> when it
         * registers/unregisters in order to take them into account when putting
         * existing calls on hold upon a new call entering its in-progress
         * state.
         *
         * @param serviceEvent
         *            the <code>ServiceEvent</code> event describing a change in
         *            the state of a service registration which may be a
         *            <code>ProtocolProviderService</code> supporting
         *            <code>OperationSetBasicTelephony</code> and thus being
         *            able to create new <code>Call</code>s
         */
        public void serviceChanged(ServiceEvent serviceEvent)
        {
            SingleCallInProgressPolicy.this.serviceChanged(serviceEvent);
        }
    }

    /**
     * Our class logger
     */
    private static final Logger logger =
        Logger.getLogger(SingleCallInProgressPolicy.class);

    /**
     * The <code>BundleContext</code> to the Calls of which this policy applies.
     */
    private final BundleContext bundleContext;

    /**
     * The <code>Call</code>s this policy manages i.e. put on hold when one of
     * them enters in progress.
     */
    private final List<Call> calls = new ArrayList<Call>();

    /**
     * The listener utilized by this policy to discover new <code>Call</code>
     * and track their in-progress state.
     */
    private final SingleCallInProgressPolicyListener listener =
        new SingleCallInProgressPolicyListener();

    /**
     * Initializes a new <code>SingleCallInProgressPolicy</code> instance which
     * will apply to the <code>Call</code>s of a specific
     * <code>BundleContext</code>.
     *
     * @param bundleContext
     *            the <code>BundleContext</code> to the
     *            <code>Call<code>s of which the new policy should apply
     */
    public SingleCallInProgressPolicy(BundleContext bundleContext)
    {
        this.bundleContext = bundleContext;

        this.bundleContext.addServiceListener(listener);
    }

    /**
     * Registers a specific <code>Call</code> with this policy in order to have
     * the rules of the latter apply to the former.
     *
     * @param call
     *            the <code>Call</code> to register with this policy in order to
     *            have the rules of the latter apply to the former
     */
    private void addCallListener(Call call)
    {
        synchronized (calls)
        {
            if (!calls.contains(call))
            {
                CallState callState = call.getCallState();

                if ((callState != null)
                    && !callState.equals(CallState.CALL_ENDED))
                {
                    calls.add(call);
                }
            }
        }

        call.addCallChangeListener(listener);
    }

    /**
     * Registers a specific <code>OperationSetBasicTelephony</code> with this
     * policy in order to have the rules of the latter apply to the
     * <code>Call</code>s created by the former.
     *
     * @param telephony
     *            the <code>OperationSetBasicTelephony</code> to register with
     *            this policy in order to have the rules of the latter apply to
     *            the <code>Call</code>s created by the former
     */
    private void addOperationSetBasicTelephonyListener(
        OperationSetBasicTelephony<? extends ProtocolProviderService> telephony)
    {
        telephony.addCallListener(listener);
    }

    /**
     * Handles changes in the state of a <code>Call</code> this policy applies
     * to in order to detect when new calls become in-progress and when the
     * other calls should be put on hold.
     *
     * @param callChangeEvent
     *            a <code>CallChangeEvent</code> value which describes the
     *            <code>Call</code> and the change in its state
     */
    private void callStateChanged(CallChangeEvent callChangeEvent)
    {
        Call call = callChangeEvent.getSourceCall();

        if (CallState.CALL_INITIALIZATION.equals(callChangeEvent.getOldValue())
                && CallState.CALL_IN_PROGRESS.equals(call.getCallState())
                && ProtocolProviderActivator
                    .getConfigurationService()
                        .getBoolean(
                            PNAME_SINGLE_CALL_IN_PROGRESS_POLICY_ENABLED,
                            true))
        {
            synchronized (calls)
            {
                for (Call otherCall : calls)
                {
                    if (!call.equals(otherCall)
                            && CallState.CALL_IN_PROGRESS
                                    .equals(otherCall.getCallState()))
                    {
                        if((call.getCallGroup() == null &&
                            otherCall.getCallGroup() == null) ||
                            (call.getCallGroup() != null ||
                                otherCall.getCallGroup() != null) &&
                            call.getCallGroup() != otherCall.getCallGroup())
                            putOnHold(otherCall);
                    }
                }
            }
        }
    }

    /**
     * Performs end-of-life cleanup associated with this instance e.g. removes
     * added listeners.
     */
    public void dispose()
    {
        bundleContext.removeServiceListener(listener);
    }

    /**
     * Handles the start and end of the <code>Call</code>s this policy applies
     * to in order to have them or stop having them put the other existing calls
     * on hold when the former change their states to
     * <code>CallState.CALL_IN_PROGRESS</code>.
     *
     * @param type
     *            one of {@link CallEvent#CALL_ENDED},
     *            {@link CallEvent#CALL_INITIATED} and
     *            {@link CallEvent#CALL_RECEIVED} which described the type of
     *            the event to be handled
     * @param callEvent
     *            a <code>CallEvent</code> value which describes the change and
     *            the <code>Call</code> associated with it
     */
    private void handleCallEvent(int type, CallEvent callEvent)
    {
        Call call = callEvent.getSourceCall();

        switch (type)
        {
        case CallEvent.CALL_ENDED:
            removeCallListener(call);
            break;

        case CallEvent.CALL_INITIATED:
        case CallEvent.CALL_RECEIVED:
            addCallListener(call);
            break;
        }
    }

    /**
     * Puts the <code>CallPeer</code>s of a specific <code>Call</code> on
     * hold.
     *
     * @param call
     *            the <code>Call</code> the <code>CallPeer</code>s of
     *            which are to be put on hold
     */
    private void putOnHold(Call call)
    {
        OperationSetBasicTelephony<?> telephony =
            call.getProtocolProvider()
                .getOperationSet(OperationSetBasicTelephony.class);

        if (telephony != null)
        {
            for (Iterator<? extends CallPeer> peerIter =
                call.getCallPeers(); peerIter.hasNext();)
            {
                CallPeer peer = peerIter.next();
                CallPeerState peerState = peer.getState();

                if (!CallPeerState.DISCONNECTED.equals(peerState)
                    && !CallPeerState.FAILED.equals(peerState)
                    && !CallPeerState.isOnHold(peerState))
                {
                    try
                    {
                        telephony.putOnHold(peer);
                    }
                    catch (OperationFailedException ex)
                    {
                        logger.error("Failed to put " + peer
                            + " on hold.", ex);
                    }
                }
            }
        }
    }

    /**
     * Unregisters a specific <code>Call</code> from this policy in order to
     * have the rules of the latter no longer applied to the former.
     *
     * @param call
     *            the <code>Call</code> to unregister from this policy in order
     *            to have the rules of the latter no longer apply to the former
     */
    private void removeCallListener(Call call)
    {
        call.removeCallChangeListener(listener);

        synchronized (calls)
        {
            calls.remove(call);
        }
    }

    /**
     * Unregisters a specific <code>OperationSetBasicTelephony</code> from this
     * policy in order to have the rules of the latter no longer apply to the
     * <code>Call</code>s created by the former.
     *
     * @param telephony
     *            the <code>OperationSetBasicTelephony</code> to unregister from
     *            this policy in order to have the rules of the latter apply to
     *            the <code>Call</code>s created by the former
     */
    private void removeOperationSetBasicTelephonyListener(
        OperationSetBasicTelephony<? extends ProtocolProviderService> telephony)
    {
        telephony.removeCallListener(listener);
    }

    /**
     * Handles the registering and unregistering of
     * <code>OperationSetBasicTelephony</code> instances in order to apply or
     * unapply the rules of this policy to the <code>Call</code>s originating
     * from them.
     *
     * @param serviceEvent
     *            a <code>ServiceEvent</code> value which described a change in
     *            a OSGi service and which is to be examined for the registering
     *            or unregistering of a <code>ProtocolProviderService</code> and
     *            thus a <code>OperationSetBasicTelephony</code>
     */
    private void serviceChanged(ServiceEvent serviceEvent)
    {
        Object service =
            bundleContext.getService(serviceEvent.getServiceReference());

        if (service instanceof ProtocolProviderService)
        {
            OperationSetBasicTelephony<?> telephony =
                ((ProtocolProviderService) service)
                    .getOperationSet(OperationSetBasicTelephony.class);

            if (telephony != null)
            {
                switch (serviceEvent.getType())
                {
                case ServiceEvent.REGISTERED:
                    addOperationSetBasicTelephonyListener(telephony);
                    break;
                case ServiceEvent.UNREGISTERING:
                    removeOperationSetBasicTelephonyListener(telephony);
                    break;
                }
            }
        }
    }
}