b7adfb1bc5b5d7ec7d37336aa7a6f6b300d99fd4
[android/platform/frameworks/opt/net/ims.git] / src / java / com / android / ims / ImsCall.java
1 /*
2  * Copyright (c) 2013 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16
17 package com.android.ims;
18
19 import java.util.ArrayList;
20 import java.util.HashMap;
21 import java.util.Iterator;
22 import java.util.Map.Entry;
23 import java.util.Set;
24
25 import android.content.Context;
26 import android.os.Bundle;
27 import android.os.Message;
28 import android.telephony.Rlog;
29
30 import com.android.ims.internal.CallGroup;
31 import com.android.ims.internal.CallGroupManager;
32 import com.android.ims.internal.ICall;
33 import com.android.ims.internal.ImsCallSession;
34 import com.android.ims.internal.ImsStreamMediaSession;
35
36 /**
37  * Handles an IMS voice / video call over LTE. You can instantiate this class with
38  * {@link ImsManager}.
39  *
40  * @hide
41  */
42 public class ImsCall implements ICall {
43     public static final int CALL_STATE_ACTIVE_TO_HOLD = 1;
44     public static final int CALL_STATE_HOLD_TO_ACTIVE = 2;
45
46     // Mode of USSD message
47     public static final int USSD_MODE_NOTIFY = 0;
48     public static final int USSD_MODE_REQUEST = 1;
49
50     private static final String TAG = "ImsCall";
51     private static final boolean DBG = true;
52
53     /**
54      * Listener for events relating to an IMS call, such as when a call is being
55      * recieved ("on ringing") or a call is outgoing ("on calling").
56      * <p>Many of these events are also received by {@link ImsCallSession.Listener}.</p>
57      */
58     public static class Listener {
59         /**
60          * Called when a request is sent out to initiate a new call
61          * and 1xx response is received from the network.
62          * The default implementation calls {@link #onCallStateChanged}.
63          *
64          * @param call the call object that carries out the IMS call
65          */
66         public void onCallProgressing(ImsCall call) {
67             onCallStateChanged(call);
68         }
69
70         /**
71          * Called when the call is established.
72          * The default implementation calls {@link #onCallStateChanged}.
73          *
74          * @param call the call object that carries out the IMS call
75          */
76         public void onCallStarted(ImsCall call) {
77             onCallStateChanged(call);
78         }
79
80         /**
81          * Called when the call setup is failed.
82          * The default implementation calls {@link #onCallError}.
83          *
84          * @param call the call object that carries out the IMS call
85          * @param reasonInfo detailed reason of the call setup failure
86          */
87         public void onCallStartFailed(ImsCall call, ImsReasonInfo reasonInfo) {
88             onCallError(call, reasonInfo);
89         }
90
91         /**
92          * Called when the call is terminated.
93          * The default implementation calls {@link #onCallStateChanged}.
94          *
95          * @param call the call object that carries out the IMS call
96          * @param reasonInfo detailed reason of the call termination
97          */
98         public void onCallTerminated(ImsCall call, ImsReasonInfo reasonInfo) {
99             // Store the call termination reason
100
101             onCallStateChanged(call);
102         }
103
104         /**
105          * Called when the call is in hold.
106          * The default implementation calls {@link #onCallStateChanged}.
107          *
108          * @param call the call object that carries out the IMS call
109          */
110         public void onCallHeld(ImsCall call) {
111             onCallStateChanged(call);
112         }
113
114         /**
115          * Called when the call hold is failed.
116          * The default implementation calls {@link #onCallError}.
117          *
118          * @param call the call object that carries out the IMS call
119          * @param reasonInfo detailed reason of the call hold failure
120          */
121         public void onCallHoldFailed(ImsCall call, ImsReasonInfo reasonInfo) {
122             onCallError(call, reasonInfo);
123         }
124
125         /**
126          * Called when the call hold is received from the remote user.
127          * The default implementation calls {@link #onCallStateChanged}.
128          *
129          * @param call the call object that carries out the IMS call
130          */
131         public void onCallHoldReceived(ImsCall call) {
132             onCallStateChanged(call);
133         }
134
135         /**
136          * Called when the call is in call.
137          * The default implementation calls {@link #onCallStateChanged}.
138          *
139          * @param call the call object that carries out the IMS call
140          */
141         public void onCallResumed(ImsCall call) {
142             onCallStateChanged(call);
143         }
144
145         /**
146          * Called when the call resume is failed.
147          * The default implementation calls {@link #onCallError}.
148          *
149          * @param call the call object that carries out the IMS call
150          * @param reasonInfo detailed reason of the call resume failure
151          */
152         public void onCallResumeFailed(ImsCall call, ImsReasonInfo reasonInfo) {
153             onCallError(call, reasonInfo);
154         }
155
156         /**
157          * Called when the call resume is received from the remote user.
158          * The default implementation calls {@link #onCallStateChanged}.
159          *
160          * @param call the call object that carries out the IMS call
161          */
162         public void onCallResumeReceived(ImsCall call) {
163             onCallStateChanged(call);
164         }
165
166         /**
167          * Called when the call is in call.
168          * The default implementation calls {@link #onCallStateChanged}.
169          *
170          * @param call the call object that carries out the IMS call
171          * @param newCall the call object that is merged with an active & hold call
172          */
173         public void onCallMerged(ImsCall call, ImsCall newCall) {
174             onCallStateChanged(call, newCall);
175         }
176
177         /**
178          * Called when the call merge is failed.
179          * The default implementation calls {@link #onCallError}.
180          *
181          * @param call the call object that carries out the IMS call
182          * @param reasonInfo detailed reason of the call merge failure
183          */
184         public void onCallMergeFailed(ImsCall call, ImsReasonInfo reasonInfo) {
185             onCallError(call, reasonInfo);
186         }
187
188         /**
189          * Called when the call is updated (except for hold/unhold).
190          * The default implementation calls {@link #onCallStateChanged}.
191          *
192          * @param call the call object that carries out the IMS call
193          */
194         public void onCallUpdated(ImsCall call) {
195             onCallStateChanged(call);
196         }
197
198         /**
199          * Called when the call update is failed.
200          * The default implementation calls {@link #onCallError}.
201          *
202          * @param call the call object that carries out the IMS call
203          * @param reasonInfo detailed reason of the call update failure
204          */
205         public void onCallUpdateFailed(ImsCall call, ImsReasonInfo reasonInfo) {
206             onCallError(call, reasonInfo);
207         }
208
209         /**
210          * Called when the call update is received from the remote user.
211          *
212          * @param call the call object that carries out the IMS call
213          */
214         public void onCallUpdateReceived(ImsCall call) {
215             // no-op
216         }
217
218         /**
219          * Called when the call is extended to the conference call.
220          * The default implementation calls {@link #onCallStateChanged}.
221          *
222          * @param call the call object that carries out the IMS call
223          * @param newCall the call object that is extended to the conference from the active call
224          */
225         public void onCallConferenceExtended(ImsCall call, ImsCall newCall) {
226             onCallStateChanged(call, newCall);
227         }
228
229         /**
230          * Called when the conference extension is failed.
231          * The default implementation calls {@link #onCallError}.
232          *
233          * @param call the call object that carries out the IMS call
234          * @param reasonInfo detailed reason of the conference extension failure
235          */
236         public void onCallConferenceExtendFailed(ImsCall call,
237                 ImsReasonInfo reasonInfo) {
238             onCallError(call, reasonInfo);
239         }
240
241         /**
242          * Called when the conference extension is received from the remote user.
243          *
244          * @param call the call object that carries out the IMS call
245          * @param newCall the call object that is extended to the conference from the active call
246          */
247         public void onCallConferenceExtendReceived(ImsCall call, ImsCall newCall) {
248             onCallStateChanged(call, newCall);
249         }
250
251         /**
252          * Called when the invitation request of the participants is delivered to
253          * the conference server.
254          *
255          * @param call the call object that carries out the IMS call
256          */
257         public void onCallInviteParticipantsRequestDelivered(ImsCall call) {
258             // no-op
259         }
260
261         /**
262          * Called when the invitation request of the participants is failed.
263          *
264          * @param call the call object that carries out the IMS call
265          * @param reasonInfo detailed reason of the conference invitation failure
266          */
267         public void onCallInviteParticipantsRequestFailed(ImsCall call,
268                 ImsReasonInfo reasonInfo) {
269             // no-op
270         }
271
272         /**
273          * Called when the removal request of the participants is delivered to
274          * the conference server.
275          *
276          * @param call the call object that carries out the IMS call
277          */
278         public void onCallRemoveParticipantsRequestDelivered(ImsCall call) {
279             // no-op
280         }
281
282         /**
283          * Called when the removal request of the participants is failed.
284          *
285          * @param call the call object that carries out the IMS call
286          * @param reasonInfo detailed reason of the conference removal failure
287          */
288         public void onCallRemoveParticipantsRequestFailed(ImsCall call,
289                 ImsReasonInfo reasonInfo) {
290             // no-op
291         }
292
293         /**
294          * Called when the conference state is updated.
295          *
296          * @param call the call object that carries out the IMS call
297          * @param state state of the participant who is participated in the conference call
298          */
299         public void onCallConferenceStateUpdated(ImsCall call, ImsConferenceState state) {
300             // no-op
301         }
302
303         /**
304          * Called when the USSD message is received from the network.
305          *
306          * @param mode mode of the USSD message (REQUEST / NOTIFY)
307          * @param ussdMessage USSD message
308          */
309         public void onCallUssdMessageReceived(ImsCall call,
310                 int mode, String ussdMessage) {
311             // no-op
312         }
313
314         /**
315          * Called when an error occurs. The default implementation is no op.
316          * overridden. The default implementation is no op. Error events are
317          * not re-directed to this callback and are handled in {@link #onCallError}.
318          *
319          * @param call the call object that carries out the IMS call
320          * @param reasonInfo detailed reason of this error
321          * @see ImsReasonInfo
322          */
323         public void onCallError(ImsCall call, ImsReasonInfo reasonInfo) {
324             // no-op
325         }
326
327         /**
328          * Called when an event occurs and the corresponding callback is not
329          * overridden. The default implementation is no op. Error events are
330          * not re-directed to this callback and are handled in {@link #onCallError}.
331          *
332          * @param call the call object that carries out the IMS call
333          */
334         public void onCallStateChanged(ImsCall call) {
335             // no-op
336         }
337
338         /**
339          * Called when an event occurs and the corresponding callback is not
340          * overridden. The default implementation is no op. Error events are
341          * not re-directed to this callback and are handled in {@link #onCallError}.
342          *
343          * @param call the call object that carries out the IMS call
344          * @param newCall the call object that will be replaced by the previous call
345          */
346         public void onCallStateChanged(ImsCall call, ImsCall newCall) {
347             // no-op
348         }
349
350         /**
351          * Called when the call moves the hold state to the conversation state.
352          * For example, when merging the active & hold call, the state of all the hold call
353          * will be changed from hold state to conversation state.
354          * This callback method can be invoked even though the application does not trigger
355          * any operations.
356          *
357          * @param call the call object that carries out the IMS call
358          * @param state the detailed state of call state changes;
359          *      Refer to CALL_STATE_* in {@link ImsCall}
360          */
361         public void onCallStateChanged(ImsCall call, int state) {
362             // no-op
363         }
364     }
365
366
367
368     // List of update operation for IMS call control
369     private static final int UPDATE_NONE = 0;
370     private static final int UPDATE_HOLD = 1;
371     private static final int UPDATE_HOLD_MERGE = 2;
372     private static final int UPDATE_RESUME = 3;
373     private static final int UPDATE_MERGE = 4;
374     private static final int UPDATE_EXTEND_TO_CONFERENCE = 5;
375     private static final int UPDATE_UNSPECIFIED = 6;
376
377     // For synchronization of private variables
378     private Object mLockObj = new Object();
379     private Context mContext;
380
381     // true if the call is established & in the conversation state
382     private boolean mInCall = false;
383     // true if the call is on hold
384     // If it is triggered by the local, mute the call. Otherwise, play local hold tone
385     // or network generated media.
386     private boolean mHold = false;
387     // true if the call is on mute
388     private boolean mMute = false;
389     // It contains the exclusive call update request. Refer to UPDATE_*.
390     private int mUpdateRequest = UPDATE_NONE;
391
392     private ImsCall.Listener mListener = null;
393     // It is for managing the multiple calls
394     // when the multiparty call is extended to the conference.
395     private CallGroup mCallGroup = null;
396
397     // Wrapper call session to interworking the IMS service (server).
398     private ImsCallSession mSession = null;
399     // Call profile of the current session.
400     // It can be changed at anytime when the call is updated.
401     private ImsCallProfile mCallProfile = null;
402     // Call profile to be updated after the application's action (accept/reject)
403     // to the call update. After the application's action (accept/reject) is done,
404     // it will be set to null.
405     private ImsCallProfile mProposedCallProfile = null;
406     private ImsReasonInfo mLastReasonInfo = null;
407
408     // Media session to control media (audio/video) operations for an IMS call
409     private ImsStreamMediaSession mMediaSession = null;
410
411     /**
412      * Create an IMS call object.
413      *
414      * @param context the context for accessing system services
415      * @param profile the call profile to make/take a call
416      */
417     public ImsCall(Context context, ImsCallProfile profile) {
418         mContext = context;
419         mCallProfile = profile;
420     }
421
422     /**
423      * Closes this object. This object is not usable after being closed.
424      */
425     @Override
426     public void close() {
427         synchronized(mLockObj) {
428             destroyCallGroup();
429
430             if (mSession != null) {
431                 mSession.close();
432                 mSession = null;
433             }
434
435             mCallProfile = null;
436             mProposedCallProfile = null;
437             mLastReasonInfo = null;
438             mMediaSession = null;
439         }
440     }
441
442     /**
443      * Checks if the call has a same remote user identity or not.
444      *
445      * @param userId the remote user identity
446      * @return true if the remote user identity is equal; otherwise, false
447      */
448     @Override
449     public boolean checkIfRemoteUserIsSame(String userId) {
450         if (userId == null) {
451             return false;
452         }
453
454         return userId.equals(mCallProfile.getCallExtra(ImsCallProfile.EXTRA_REMOTE_URI, ""));
455     }
456
457     /**
458      * Checks if the call is equal or not.
459      *
460      * @param call the call to be compared
461      * @return true if the call is equal; otherwise, false
462      */
463     @Override
464     public boolean equalsTo(ICall call) {
465         if (call == null) {
466             return false;
467         }
468
469         if (call instanceof ImsCall) {
470             return this.equals((ImsCall)call);
471         }
472
473         return false;
474     }
475
476     /**
477      * Gets the negotiated (local & remote) call profile.
478      *
479      * @return a {@link ImsCallProfile} object that has the negotiated call profile
480      */
481     public ImsCallProfile getCallProfile() {
482         synchronized(mLockObj) {
483             return mCallProfile;
484         }
485     }
486
487     /**
488      * Gets the local call profile (local capabilities).
489      *
490      * @return a {@link ImsCallProfile} object that has the local call profile
491      */
492     public ImsCallProfile getLocalCallProfile() throws ImsException {
493         synchronized(mLockObj) {
494             if (mSession == null) {
495                 throw new ImsException("No call session",
496                         ImsReasonInfo.CODE_LOCAL_CALL_TERMINATED);
497             }
498
499             try {
500                 return mSession.getLocalCallProfile();
501             } catch (Throwable t) {
502                 loge("getLocalCallProfile :: ", t);
503                 throw new ImsException("getLocalCallProfile()", t, 0);
504             }
505         }
506     }
507
508     /**
509      * Gets the call profile proposed by the local/remote user.
510      *
511      * @return a {@link ImsCallProfile} object that has the proposed call profile
512      */
513     public ImsCallProfile getProposedCallProfile() {
514         synchronized(mLockObj) {
515             if (!isInCall()) {
516                 return null;
517             }
518
519             return mProposedCallProfile;
520         }
521     }
522
523     /**
524      * Gets the state of the {@link ImsCallSession} that carries this call.
525      * The value returned must be one of the states in {@link ImsCallSession#State}.
526      *
527      * @return the session state
528      */
529     public int getState() {
530         synchronized(mLockObj) {
531             if (mSession == null) {
532                 return ImsCallSession.State.IDLE;
533             }
534
535             return mSession.getState();
536         }
537     }
538
539     /**
540      * Gets the {@link ImsCallSession} that carries this call.
541      *
542      * @return the session object that carries this call
543      * @hide
544      */
545     public ImsCallSession getCallSession() {
546         synchronized(mLockObj) {
547             return mSession;
548         }
549     }
550
551     /**
552      * Gets the {@link ImsStreamMediaSession} that handles the media operation of this call.
553      * Almost interface APIs are for the VT (Video Telephony).
554      *
555      * @return the media session object that handles the media operation of this call
556      * @hide
557      */
558     public ImsStreamMediaSession getMediaSession() {
559         synchronized(mLockObj) {
560             return mMediaSession;
561         }
562     }
563
564     /**
565      * Gets the specified property of this call.
566      *
567      * @param name key to get the extra call information defined in {@link ImsCallProfile}
568      * @return the extra call information as string
569      */
570     public String getCallExtra(String name) throws ImsException {
571         // Lookup the cache
572
573         synchronized(mLockObj) {
574             // If not found, try to get the property from the remote
575             if (mSession == null) {
576                 throw new ImsException("No call session",
577                         ImsReasonInfo.CODE_LOCAL_CALL_TERMINATED);
578             }
579
580             try {
581                 return mSession.getProperty(name);
582             } catch (Throwable t) {
583                 loge("getCallExtra :: ", t);
584                 throw new ImsException("getCallExtra()", t, 0);
585             }
586         }
587     }
588
589     /**
590      * Gets the last reason information when the call is not established, cancelled or terminated.
591      *
592      * @return the last reason information
593      */
594     public ImsReasonInfo getLastReasonInfo() {
595         synchronized(mLockObj) {
596             return mLastReasonInfo;
597         }
598     }
599
600     /**
601      * Checks if the call has a pending update operation.
602      *
603      * @return true if the call has a pending update operation
604      */
605     public boolean hasPendingUpdate() {
606         synchronized(mLockObj) {
607             return (mUpdateRequest != UPDATE_NONE);
608         }
609     }
610
611     /**
612      * Checks if the call is established.
613      *
614      * @return true if the call is established
615      */
616     public boolean isInCall() {
617         synchronized(mLockObj) {
618             return mInCall;
619         }
620     }
621
622     /**
623      * Checks if the call is muted.
624      *
625      * @return true if the call is muted
626      */
627     public boolean isMuted() {
628         synchronized(mLockObj) {
629             return mMute;
630         }
631     }
632
633     /**
634      * Checks if the call is on hold.
635      *
636      * @return true if the call is on hold
637      */
638     public boolean isOnHold() {
639         synchronized(mLockObj) {
640             return mHold;
641         }
642     }
643
644     /**
645      * Sets the listener to listen to the IMS call events.
646      * The method calls {@link #setListener setListener(listener, false)}.
647      *
648      * @param listener to listen to the IMS call events of this object; null to remove listener
649      * @see #setListener(Listener, boolean)
650      */
651     public void setListener(ImsCall.Listener listener) {
652         setListener(listener, false);
653     }
654
655     /**
656      * Sets the listener to listen to the IMS call events.
657      * A {@link ImsCall} can only hold one listener at a time. Subsequent calls
658      * to this method override the previous listener.
659      *
660      * @param listener to listen to the IMS call events of this object; null to remove listener
661      * @param callbackImmediately set to true if the caller wants to be called
662      *        back immediately on the current state
663      */
664     public void setListener(ImsCall.Listener listener, boolean callbackImmediately) {
665         boolean inCall;
666         boolean onHold;
667         int state;
668         ImsReasonInfo lastReasonInfo;
669
670         synchronized(mLockObj) {
671             mListener = listener;
672
673             if ((listener == null) || !callbackImmediately) {
674                 return;
675             }
676
677             inCall = mInCall;
678             onHold = mHold;
679             state = getState();
680             lastReasonInfo = mLastReasonInfo;
681         }
682
683         try {
684             if (lastReasonInfo != null) {
685                 listener.onCallError(this, lastReasonInfo);
686             } else if (inCall) {
687                 if (onHold) {
688                     listener.onCallHeld(this);
689                 } else {
690                     listener.onCallStarted(this);
691                 }
692             } else {
693                 switch (state) {
694                     case ImsCallSession.State.ESTABLISHING:
695                         listener.onCallProgressing(this);
696                         break;
697                     case ImsCallSession.State.TERMINATED:
698                         listener.onCallTerminated(this, lastReasonInfo);
699                         break;
700                     default:
701                         // Ignore it. There is no action in the other state.
702                         break;
703                 }
704             }
705         } catch (Throwable t) {
706             loge("setListener()", t);
707         }
708     }
709
710     /**
711      * Mutes or unmutes the mic for the active call.
712      *
713      * @param muted true if the call is muted, false otherwise
714      */
715     public void setMute(boolean muted) throws ImsException {
716         synchronized(mLockObj) {
717             if (mMute != muted) {
718                 mMute = muted;
719
720                 try {
721                     mSession.setMute(muted);
722                 } catch (Throwable t) {
723                     loge("setMute :: ", t);
724                     throwImsException(t, 0);
725                 }
726             }
727         }
728     }
729
730      /**
731       * Attaches an incoming call to this call object.
732       *
733       * @param session the session that receives the incoming call
734       * @throws ImsException if the IMS service fails to attach this object to the session
735       */
736      public void attachSession(ImsCallSession session) throws ImsException {
737          if (DBG) {
738              log("attachSession :: session=" + session);
739          }
740
741          synchronized(mLockObj) {
742              mSession = session;
743
744              try {
745                  mSession.setListener(createCallSessionListener());
746              } catch (Throwable t) {
747                  loge("attachSession :: ", t);
748                  throwImsException(t, 0);
749              }
750          }
751      }
752
753     /**
754      * Initiates an IMS call with the call profile which is provided
755      * when creating a {@link ImsCall}.
756      *
757      * @param session the {@link ImsCallSession} for carrying out the call
758      * @param callee callee information to initiate an IMS call
759      * @throws ImsException if the IMS service fails to initiate the call
760      */
761     public void start(ImsCallSession session, String callee)
762             throws ImsException {
763         if (DBG) {
764             log("start(1) :: session=" + session + ", callee=" + callee);
765         }
766
767         synchronized(mLockObj) {
768             mSession = session;
769
770             try {
771                 session.setListener(createCallSessionListener());
772                 session.start(callee, mCallProfile);
773             } catch (Throwable t) {
774                 loge("start(1) :: ", t);
775                 throw new ImsException("start(1)", t, 0);
776             }
777         }
778     }
779
780     /**
781      * Initiates an IMS conferenca call with the call profile which is provided
782      * when creating a {@link ImsCall}.
783      *
784      * @param session the {@link ImsCallSession} for carrying out the call
785      * @param participants participant list to initiate an IMS conference call
786      * @throws ImsException if the IMS service fails to initiate the call
787      */
788     public void start(ImsCallSession session, String[] participants)
789             throws ImsException {
790         if (DBG) {
791             log("start(n) :: session=" + session + ", callee=" + participants);
792         }
793
794         synchronized(mLockObj) {
795             mSession = session;
796
797             try {
798                 session.setListener(createCallSessionListener());
799                 session.start(participants, mCallProfile);
800             } catch (Throwable t) {
801                 loge("start(n) :: ", t);
802                 throw new ImsException("start(n)", t, 0);
803             }
804         }
805     }
806
807     /**
808      * Accepts a call.
809      *
810      * @see Listener#onCallStarted
811      * @throws ImsException if the IMS service fails to accept the call
812      */
813     public void accept() throws ImsException {
814         if (DBG) {
815             log("accept :: session=" + mSession);
816         }
817
818         accept(ImsCallProfile.CALL_TYPE_VOICE, new ImsStreamMediaProfile());
819     }
820
821     /**
822      * Accepts a call.
823      *
824      * @param callType call type to be answered in {@link ImsCallProfile}
825      * @param profile a media profile to be answered (audio/audio & video, direction, ...)
826      * @see Listener#onCallStarted
827      * @throws ImsException if the IMS service fails to accept the call
828      */
829     public void accept(int callType, ImsStreamMediaProfile profile) throws ImsException {
830         if (DBG) {
831             log("accept :: session=" + mSession
832                     + ", callType=" + callType + ", profile=" + profile);
833         }
834
835         synchronized(mLockObj) {
836             if (mSession == null) {
837                 throw new ImsException("No call to answer",
838                         ImsReasonInfo.CODE_LOCAL_CALL_TERMINATED);
839             }
840
841             try {
842                 mSession.accept(callType, profile);
843             } catch (Throwable t) {
844                 loge("accept :: ", t);
845                 throw new ImsException("accept()", t, 0);
846             }
847
848             if (mInCall && (mProposedCallProfile != null)) {
849                 if (DBG) {
850                     log("accept :: call profile will be updated");
851                 }
852
853                 mCallProfile = mProposedCallProfile;
854                 mProposedCallProfile = null;
855             }
856
857             // Other call update received
858             if (mInCall && (mUpdateRequest == UPDATE_UNSPECIFIED)) {
859                 mUpdateRequest = UPDATE_NONE;
860             }
861         }
862     }
863
864     /**
865      * Rejects a call.
866      *
867      * @param reason reason code to reject an incoming call
868      * @see Listener#onCallStartFailed
869      * @throws ImsException if the IMS service fails to accept the call
870      */
871     public void reject(int reason) throws ImsException {
872         if (DBG) {
873             log("reject :: session=" + mSession + ", reason=" + reason);
874         }
875
876         synchronized(mLockObj) {
877             if (mSession != null) {
878                 mSession.reject(reason);
879             }
880
881             if (mInCall && (mProposedCallProfile != null)) {
882                 if (DBG) {
883                     log("reject :: call profile is not updated; destroy it...");
884                 }
885
886                 mProposedCallProfile = null;
887             }
888
889             // Other call update received
890             if (mInCall && (mUpdateRequest == UPDATE_UNSPECIFIED)) {
891                 mUpdateRequest = UPDATE_NONE;
892             }
893         }
894     }
895
896     /**
897      * Terminates an IMS call.
898      *
899      * @param reason reason code to terminate a call
900      * @throws ImsException if the IMS service fails to terminate the call
901      */
902     public void terminate(int reason) throws ImsException {
903         if (DBG) {
904             log("terminate :: session=" + mSession + ", reason=" + reason);
905         }
906
907         synchronized(mLockObj) {
908             mHold = false;
909             mInCall = false;
910
911             if (mSession != null) {
912                 mSession.terminate(reason);
913             }
914         }
915     }
916
917     /**
918      * Puts a call on hold. When succeeds, {@link Listener#onCallHeld} is called.
919      *
920      * @see Listener#onCallHeld, Listener#onCallHoldFailed
921      * @throws ImsException if the IMS service fails to hold the call
922      */
923     public void hold() throws ImsException {
924         if (DBG) {
925             log("hold :: session=" + mSession);
926         }
927
928         if (isOnHold()) {
929             if (DBG) {
930                 log("hold :: call is already on hold");
931             }
932             return;
933         }
934
935         synchronized(mLockObj) {
936             if (mUpdateRequest != UPDATE_NONE) {
937                 loge("hold :: update is in progress; request=" + mUpdateRequest);
938                 throw new ImsException("Call update is in progress",
939                         ImsReasonInfo.CODE_LOCAL_ILLEGAL_STATE);
940             }
941
942             if (mSession == null) {
943                 loge("hold :: ");
944                 throw new ImsException("No call session",
945                         ImsReasonInfo.CODE_LOCAL_CALL_TERMINATED);
946             }
947
948             mSession.hold(createHoldMediaProfile());
949             // FIXME: update the state on the callback?
950             mHold = true;
951             mUpdateRequest = UPDATE_HOLD;
952         }
953     }
954
955     /**
956      * Continues a call that's on hold. When succeeds, {@link Listener#onCallResumed} is called.
957      *
958      * @see Listener#onCallResumed, Listener#onCallResumeFailed
959      * @throws ImsException if the IMS service fails to resume the call
960      */
961     public void resume() throws ImsException {
962         if (DBG) {
963             log("resume :: session=" + mSession);
964         }
965
966         if (!isOnHold()) {
967             if (DBG) {
968                 log("resume :: call is in conversation");
969             }
970             return;
971         }
972
973         synchronized(mLockObj) {
974             if (mUpdateRequest != UPDATE_NONE) {
975                 loge("resume :: update is in progress; request=" + mUpdateRequest);
976                 throw new ImsException("Call update is in progress",
977                         ImsReasonInfo.CODE_LOCAL_ILLEGAL_STATE);
978             }
979
980             if (mSession == null) {
981                 loge("resume :: ");
982                 throw new ImsException("No call session",
983                         ImsReasonInfo.CODE_LOCAL_CALL_TERMINATED);
984             }
985
986             mSession.resume(createResumeMediaProfile());
987             // FIXME: update the state on the callback?
988             mHold = false;
989             mUpdateRequest = UPDATE_RESUME;
990         }
991     }
992
993     /**
994      * Merges the active & hold call.
995      *
996      * @see Listener#onCallMerged, Listener#onCallMergeFailed
997      * @throws ImsException if the IMS service fails to merge the call
998      */
999     public void merge() throws ImsException {
1000         if (DBG) {
1001             log("merge :: session=" + mSession);
1002         }
1003
1004         synchronized(mLockObj) {
1005             if (mUpdateRequest != UPDATE_NONE) {
1006                 loge("merge :: update is in progress; request=" + mUpdateRequest);
1007                 throw new ImsException("Call update is in progress",
1008                         ImsReasonInfo.CODE_LOCAL_ILLEGAL_STATE);
1009             }
1010
1011             if (mSession == null) {
1012                 loge("merge :: ");
1013                 throw new ImsException("No call session",
1014                         ImsReasonInfo.CODE_LOCAL_CALL_TERMINATED);
1015             }
1016
1017             if (mHold) {
1018                 mSession.merge();
1019                 mUpdateRequest = UPDATE_MERGE;
1020             } else {
1021                 mSession.hold(createHoldMediaProfile());
1022                 // FIXME: ?
1023                 mHold = true;
1024                 mUpdateRequest = UPDATE_HOLD_MERGE;
1025             }
1026         }
1027     }
1028
1029     /**
1030      * Merges the active & hold call.
1031      *
1032      * @param bgCall the background (holding) call
1033      * @see Listener#onCallMerged, Listener#onCallMergeFailed
1034      * @throws ImsException if the IMS service fails to merge the call
1035      */
1036     public void merge(ImsCall bgCall) throws ImsException {
1037         if (DBG) {
1038             log("merge(1) :: session=" + mSession);
1039         }
1040
1041         if (bgCall == null) {
1042             throw new ImsException("No background call",
1043                     ImsReasonInfo.CODE_LOCAL_ILLEGAL_ARGUMENT);
1044         }
1045
1046         synchronized(mLockObj) {
1047             createCallGroup(bgCall);
1048         }
1049
1050         merge();
1051     }
1052
1053     /**
1054      * Updates the current call's properties (ex. call mode change: video upgrade / downgrade).
1055      */
1056     public void update(int callType, ImsStreamMediaProfile mediaProfile) throws ImsException {
1057         if (DBG) {
1058             log("update :: session=" + mSession);
1059         }
1060
1061         if (isOnHold()) {
1062             if (DBG) {
1063                 log("update :: call is on hold");
1064             }
1065             throw new ImsException("Not in a call to update call",
1066                     ImsReasonInfo.CODE_LOCAL_ILLEGAL_STATE);
1067         }
1068
1069         synchronized(mLockObj) {
1070             if (mUpdateRequest != UPDATE_NONE) {
1071                 if (DBG) {
1072                     log("update :: update is in progress; request=" + mUpdateRequest);
1073                 }
1074                 throw new ImsException("Call update is in progress",
1075                         ImsReasonInfo.CODE_LOCAL_ILLEGAL_STATE);
1076             }
1077
1078             if (mSession == null) {
1079                 loge("update :: ");
1080                 throw new ImsException("No call session",
1081                         ImsReasonInfo.CODE_LOCAL_CALL_TERMINATED);
1082             }
1083
1084             mSession.update(callType, mediaProfile);
1085             mUpdateRequest = UPDATE_UNSPECIFIED;
1086         }
1087     }
1088
1089     /**
1090      * Extends this call (1-to-1 call) to the conference call
1091      * inviting the specified participants to.
1092      *
1093      */
1094     public void extendToConference(String[] participants) throws ImsException {
1095         if (DBG) {
1096             log("extendToConference :: session=" + mSession);
1097         }
1098
1099         if (isOnHold()) {
1100             if (DBG) {
1101                 log("extendToConference :: call is on hold");
1102             }
1103             throw new ImsException("Not in a call to extend a call to conference",
1104                     ImsReasonInfo.CODE_LOCAL_ILLEGAL_STATE);
1105         }
1106
1107         synchronized(mLockObj) {
1108             if (mUpdateRequest != UPDATE_NONE) {
1109                 if (DBG) {
1110                     log("extendToConference :: update is in progress; request=" + mUpdateRequest);
1111                 }
1112                 throw new ImsException("Call update is in progress",
1113                         ImsReasonInfo.CODE_LOCAL_ILLEGAL_STATE);
1114             }
1115
1116             if (mSession == null) {
1117                 loge("extendToConference :: ");
1118                 throw new ImsException("No call session",
1119                         ImsReasonInfo.CODE_LOCAL_CALL_TERMINATED);
1120             }
1121
1122             mSession.extendToConference(participants);
1123             mUpdateRequest = UPDATE_EXTEND_TO_CONFERENCE;
1124         }
1125     }
1126
1127     /**
1128      * Requests the conference server to invite an additional participants to the conference.
1129      *
1130      */
1131     public void inviteParticipants(String[] participants) throws ImsException {
1132         if (DBG) {
1133             log("inviteParticipants :: session=" + mSession);
1134         }
1135
1136         synchronized(mLockObj) {
1137             if (mSession == null) {
1138                 loge("inviteParticipants :: ");
1139                 throw new ImsException("No call session",
1140                         ImsReasonInfo.CODE_LOCAL_CALL_TERMINATED);
1141             }
1142
1143             mSession.inviteParticipants(participants);
1144         }
1145     }
1146
1147     /**
1148      * Requests the conference server to remove the specified participants from the conference.
1149      *
1150      */
1151     public void removeParticipants(String[] participants) throws ImsException {
1152         if (DBG) {
1153             log("removeParticipants :: session=" + mSession);
1154         }
1155
1156         synchronized(mLockObj) {
1157             if (mSession == null) {
1158                 loge("removeParticipants :: ");
1159                 throw new ImsException("No call session",
1160                         ImsReasonInfo.CODE_LOCAL_CALL_TERMINATED);
1161             }
1162
1163             mSession.removeParticipants(participants);
1164         }
1165     }
1166
1167
1168     /**
1169      * Sends a DTMF code. According to <a href="http://tools.ietf.org/html/rfc2833">RFC 2833</a>,
1170      * event 0 ~ 9 maps to decimal value 0 ~ 9, '*' to 10, '#' to 11, event 'A' ~ 'D' to 12 ~ 15,
1171      * and event flash to 16. Currently, event flash is not supported.
1172      *
1173      * @param code the DTMF to send. Value 0 to 15 (inclusive) are valid inputs.
1174      */
1175     public void sendDtmf(int code) {
1176         sendDtmf(code, null);
1177     }
1178
1179     /**
1180      * Sends a DTMF code. According to <a href="http://tools.ietf.org/html/rfc2833">RFC 2833</a>,
1181      * event 0 ~ 9 maps to decimal value 0 ~ 9, '*' to 10, '#' to 11, event 'A' ~ 'D' to 12 ~ 15,
1182      * and event flash to 16. Currently, event flash is not supported.
1183      *
1184      * @param code the DTMF to send. Value 0 to 15 (inclusive) are valid inputs.
1185      * @param result the result message to send when done
1186      */
1187     public void sendDtmf(int code, Message result) {
1188         if (DBG) {
1189             log("sendDtmf :: session=" + mSession + ", code=" + code);
1190         }
1191
1192         synchronized(mLockObj) {
1193             if (mSession != null) {
1194                 mSession.sendDtmf(code);
1195             }
1196         }
1197
1198         if (result != null) {
1199             result.sendToTarget();
1200         }
1201     }
1202
1203     /**
1204      * Sends an USSD message.
1205      *
1206      * @param ussdMessage USSD message to send
1207      */
1208     public void sendUssd(String ussdMessage) throws ImsException {
1209         if (DBG) {
1210             log("sendUssd :: session=" + mSession + ", ussdMessage=" + ussdMessage);
1211         }
1212
1213         synchronized(mLockObj) {
1214             if (mSession == null) {
1215                 loge("sendUssd :: ");
1216                 throw new ImsException("No call session",
1217                         ImsReasonInfo.CODE_LOCAL_CALL_TERMINATED);
1218             }
1219
1220             mSession.sendUssd(ussdMessage);
1221         }
1222     }
1223
1224     private void clear(ImsReasonInfo lastReasonInfo) {
1225         mInCall = false;
1226         mHold = false;
1227         mUpdateRequest = UPDATE_NONE;
1228         mLastReasonInfo = lastReasonInfo;
1229         destroyCallGroup();
1230     }
1231
1232     private void createCallGroup(ImsCall neutralReferrer) {
1233         CallGroup referrerCallGroup = neutralReferrer.getCallGroup();
1234
1235         if (mCallGroup == null) {
1236             if (referrerCallGroup == null) {
1237                 mCallGroup = CallGroupManager.getInstance().createCallGroup(new ImsCallGroup());
1238             } else {
1239                 mCallGroup = referrerCallGroup;
1240             }
1241
1242             if (mCallGroup != null) {
1243                 mCallGroup.setNeutralReferrer(neutralReferrer);
1244             }
1245         } else {
1246             mCallGroup.setNeutralReferrer(neutralReferrer);
1247
1248             if ((referrerCallGroup != null)
1249                     && (mCallGroup != referrerCallGroup)) {
1250                 loge("fatal :: call group is mismatched; call is corrupted...");
1251             }
1252         }
1253     }
1254
1255     private void updateCallGroup(ImsCall owner) {
1256         if (mCallGroup == null) {
1257             return;
1258         }
1259
1260         ImsCall neutralReferrer = (ImsCall)mCallGroup.getNeutralReferrer();
1261
1262         mCallGroup.setNeutralReferrer(null);
1263
1264         if (owner == null) {
1265             // Maintain the call group if the current call has been merged in the past.
1266             if (!mCallGroup.hasReferrer()) {
1267                 CallGroupManager.getInstance().destroyCallGroup(mCallGroup);
1268                 mCallGroup = null;
1269             }
1270         } else {
1271             mCallGroup.addReferrer(this);
1272
1273             if (neutralReferrer != null) {
1274                 if (neutralReferrer.isInCall() && (neutralReferrer.getCallGroup() == null)) {
1275                     neutralReferrer.setCallGroup(mCallGroup);
1276                     mCallGroup.addReferrer(neutralReferrer);
1277                 }
1278
1279                 neutralReferrer.enforceConversationMode();
1280             }
1281
1282             // Close the existing owner call if present
1283             ImsCall exOwner = (ImsCall)mCallGroup.getOwner();
1284
1285             mCallGroup.setOwner(owner);
1286
1287             if (exOwner != null) {
1288                 exOwner.close();
1289             }
1290         }
1291     }
1292
1293     private void destroyCallGroup() {
1294         if (mCallGroup == null) {
1295             return;
1296         }
1297
1298         mCallGroup.removeReferrer(this);
1299
1300         if (!mCallGroup.hasReferrer()) {
1301             CallGroupManager.getInstance().destroyCallGroup(mCallGroup);
1302         }
1303
1304         mCallGroup = null;
1305     }
1306
1307     private CallGroup getCallGroup() {
1308         synchronized(mLockObj) {
1309             return mCallGroup;
1310         }
1311     }
1312
1313     private void setCallGroup(CallGroup callGroup) {
1314         synchronized(mLockObj) {
1315             mCallGroup = callGroup;
1316         }
1317     }
1318
1319     /**
1320      * Creates an IMS call session listener.
1321      */
1322     private ImsCallSession.Listener createCallSessionListener() {
1323         return new ImsCallSessionListenerProxy();
1324     }
1325
1326     private ImsCall createNewCall(ImsCallSession session, ImsCallProfile profile) {
1327         ImsCall call = new ImsCall(mContext, profile);
1328
1329         try {
1330             call.attachSession(session);
1331         } catch (ImsException e) {
1332             if (call != null) {
1333                 call.close();
1334                 call = null;
1335             }
1336         }
1337
1338         // Do additional operations...
1339
1340         return call;
1341     }
1342
1343     private ImsStreamMediaProfile createHoldMediaProfile() {
1344         ImsStreamMediaProfile mediaProfile = new ImsStreamMediaProfile();
1345
1346         if (mCallProfile == null) {
1347             return mediaProfile;
1348         }
1349
1350         mediaProfile.mAudioQuality = mCallProfile.mMediaProfile.mAudioQuality;
1351         mediaProfile.mVideoQuality = mCallProfile.mMediaProfile.mVideoQuality;
1352         mediaProfile.mAudioDirection = ImsStreamMediaProfile.DIRECTION_SEND;
1353
1354         if (mediaProfile.mVideoQuality != ImsStreamMediaProfile.VIDEO_QUALITY_NONE) {
1355             mediaProfile.mVideoDirection = ImsStreamMediaProfile.DIRECTION_SEND;
1356         }
1357
1358         return mediaProfile;
1359     }
1360
1361     private ImsStreamMediaProfile createResumeMediaProfile() {
1362         ImsStreamMediaProfile mediaProfile = new ImsStreamMediaProfile();
1363
1364         if (mCallProfile == null) {
1365             return mediaProfile;
1366         }
1367
1368         mediaProfile.mAudioQuality = mCallProfile.mMediaProfile.mAudioQuality;
1369         mediaProfile.mVideoQuality = mCallProfile.mMediaProfile.mVideoQuality;
1370         mediaProfile.mAudioDirection = ImsStreamMediaProfile.DIRECTION_SEND_RECEIVE;
1371
1372         if (mediaProfile.mVideoQuality != ImsStreamMediaProfile.VIDEO_QUALITY_NONE) {
1373             mediaProfile.mVideoDirection = ImsStreamMediaProfile.DIRECTION_SEND_RECEIVE;
1374         }
1375
1376         return mediaProfile;
1377     }
1378
1379     private void enforceConversationMode() {
1380         if (mInCall) {
1381             mHold = false;
1382             mUpdateRequest = UPDATE_NONE;
1383         }
1384     }
1385
1386     private void mergeInternal() {
1387         if (DBG) {
1388             log("mergeInternal :: session=" + mSession);
1389         }
1390
1391         mSession.merge();
1392         mUpdateRequest = UPDATE_MERGE;
1393     }
1394
1395     private void notifyCallStateChanged() {
1396         int state = 0;
1397
1398         if (mInCall && (mUpdateRequest == UPDATE_HOLD_MERGE)) {
1399             state = CALL_STATE_ACTIVE_TO_HOLD;
1400             mHold = true;
1401         } else if (mInCall && ((mUpdateRequest == UPDATE_MERGE)
1402                 || (mUpdateRequest == UPDATE_EXTEND_TO_CONFERENCE))) {
1403             state = CALL_STATE_HOLD_TO_ACTIVE;
1404             mHold = false;
1405             mMute = false;
1406         }
1407
1408         if (state != 0) {
1409             if (mListener != null) {
1410                 try {
1411                     mListener.onCallStateChanged(ImsCall.this, state);
1412                 } catch (Throwable t) {
1413                     loge("notifyCallStateChanged :: ", t);
1414                 }
1415             }
1416         }
1417     }
1418
1419     private void notifyConferenceSessionTerminated(ImsReasonInfo reasonInfo) {
1420         ImsCall.Listener listener;
1421
1422         if (mCallGroup.isOwner(ImsCall.this)) {
1423             ArrayList<ICall> referrers = mCallGroup.getReferrers();
1424
1425             if (referrers != null) {
1426                 for (int i = 0; i < referrers.size(); ++i) {
1427                     ImsCall call = (ImsCall)referrers.get(i);
1428
1429                     if (call == null) {
1430                         continue;
1431                     }
1432
1433                     listener = call.mListener;
1434                     call.clear(reasonInfo);
1435
1436                     if (listener != null) {
1437                         try {
1438                             listener.onCallTerminated(call, reasonInfo);
1439                         } catch (Throwable t) {
1440                             loge("notifyConferenceSessionTerminated :: ", t);
1441                         }
1442                     }
1443                 }
1444             }
1445         } else if (!mCallGroup.isReferrer(ImsCall.this)) {
1446             return;
1447         }
1448
1449         listener = mListener;
1450         clear(reasonInfo);
1451
1452         if (listener != null) {
1453             try {
1454                 listener.onCallTerminated(this, reasonInfo);
1455             } catch (Throwable t) {
1456                 loge("notifyConferenceSessionTerminated :: ", t);
1457             }
1458         }
1459     }
1460
1461     private void notifyConferenceStateUpdated(ImsConferenceState state) {
1462         Set<Entry<String, Bundle>> paticipants = state.mParticipants.entrySet();
1463
1464         if (paticipants == null) {
1465             return;
1466         }
1467
1468         Iterator<Entry<String, Bundle>> iterator = paticipants.iterator();
1469
1470         while (iterator.hasNext()) {
1471             Entry<String, Bundle> entry = iterator.next();
1472
1473             String key = entry.getKey();
1474             Bundle confInfo = entry.getValue();
1475             String status = confInfo.getString(ImsConferenceState.STATUS);
1476             String user = confInfo.getString(ImsConferenceState.USER);
1477             String endpoint = confInfo.getString(ImsConferenceState.ENDPOINT);
1478
1479             if (DBG) {
1480                 log("notifyConferenceStateUpdated :: key=" + key +
1481                         ", status=" + status +
1482                         ", user=" + user +
1483                         ", endpoint=" + endpoint);
1484             }
1485
1486             if ((mCallGroup != null) && (!mCallGroup.isOwner(ImsCall.this))) {
1487                 continue;
1488             }
1489
1490             ImsCall referrer = (ImsCall)mCallGroup.getReferrer(endpoint);
1491
1492             if (referrer == null) {
1493                 continue;
1494             }
1495
1496             if (referrer.mListener == null) {
1497                 continue;
1498             }
1499
1500             try {
1501                 if (status.equals(ImsConferenceState.STATUS_ALERTING)) {
1502                     referrer.mListener.onCallProgressing(referrer);
1503                 }
1504                 else if (status.equals(ImsConferenceState.STATUS_CONNECT_FAIL)) {
1505                     referrer.mListener.onCallStartFailed(referrer, new ImsReasonInfo());
1506                 }
1507                 else if (status.equals(ImsConferenceState.STATUS_ON_HOLD)) {
1508                     referrer.mListener.onCallHoldReceived(referrer);
1509                 }
1510                 else if (status.equals(ImsConferenceState.STATUS_CONNECTED)) {
1511                     referrer.mListener.onCallStarted(referrer);
1512                 }
1513                 else if (status.equals(ImsConferenceState.STATUS_DISCONNECTED)) {
1514                     referrer.clear(new ImsReasonInfo());
1515                     referrer.mListener.onCallTerminated(referrer, referrer.mLastReasonInfo);
1516                 }
1517             } catch (Throwable t) {
1518                 loge("notifyConferenceStateUpdated :: ", t);
1519             }
1520         }
1521     }
1522
1523     private void notifyError(int reason, int statusCode, String message) {
1524     }
1525
1526     private void throwImsException(Throwable t, int code) throws ImsException {
1527         if (t instanceof ImsException) {
1528             throw (ImsException) t;
1529         } else {
1530             throw new ImsException(String.valueOf(code), t, code);
1531         }
1532     }
1533
1534     private void log(String s) {
1535         Rlog.d(TAG, s);
1536     }
1537
1538     private void loge(String s) {
1539         Rlog.e(TAG, s);
1540     }
1541
1542     private void loge(String s, Throwable t) {
1543         Rlog.e(TAG, s, t);
1544     }
1545
1546     private class ImsCallSessionListenerProxy extends ImsCallSession.Listener {
1547         @Override
1548         public void callSessionProgressing(ImsCallSession session,
1549                 ImsStreamMediaProfile profile) {
1550             if (DBG) {
1551                 log("callSessionProgressing :: session=" + session + ", profile=" + profile);
1552             }
1553
1554             ImsCall.Listener listener;
1555
1556             synchronized(ImsCall.this) {
1557                 listener = mListener;
1558                 mCallProfile.mMediaProfile.copyFrom(profile);
1559             }
1560
1561             if (listener != null) {
1562                 try {
1563                     listener.onCallProgressing(ImsCall.this);
1564                 } catch (Throwable t) {
1565                     loge("callSessionProgressing :: ", t);
1566                 }
1567             }
1568         }
1569
1570         @Override
1571         public void callSessionStarted(ImsCallSession session,
1572                 ImsCallProfile profile) {
1573             if (DBG) {
1574                 log("callSessionStarted :: session=" + session + ", profile=" + profile);
1575             }
1576
1577             ImsCall.Listener listener;
1578
1579             synchronized(ImsCall.this) {
1580                 listener = mListener;
1581                 mCallProfile = profile;
1582             }
1583
1584             if (listener != null) {
1585                 try {
1586                     listener.onCallStarted(ImsCall.this);
1587                 } catch (Throwable t) {
1588                     loge("callSessionStarted :: ", t);
1589                 }
1590             }
1591         }
1592
1593         @Override
1594         public void callSessionStartFailed(ImsCallSession session,
1595                 ImsReasonInfo reasonInfo) {
1596             if (DBG) {
1597                 log("callSessionStartFailed :: session=" + session +
1598                         ", reasonInfo=" + reasonInfo);
1599             }
1600
1601             ImsCall.Listener listener;
1602
1603             synchronized(ImsCall.this) {
1604                 listener = mListener;
1605                 mLastReasonInfo = reasonInfo;
1606             }
1607
1608             if (listener != null) {
1609                 try {
1610                     listener.onCallStartFailed(ImsCall.this, reasonInfo);
1611                 } catch (Throwable t) {
1612                     loge("callSessionStarted :: ", t);
1613                 }
1614             }
1615         }
1616
1617         @Override
1618         public void callSessionTerminated(ImsCallSession session,
1619                 ImsReasonInfo reasonInfo) {
1620             if (DBG) {
1621                 log("callSessionTerminated :: session=" + session +
1622                         ", reasonInfo=" + reasonInfo);
1623             }
1624
1625             ImsCall.Listener listener = null;
1626
1627             synchronized(ImsCall.this) {
1628                 if (mCallGroup != null) {
1629                     notifyConferenceSessionTerminated(reasonInfo);
1630                 } else {
1631                     listener = mListener;
1632                     clear(reasonInfo);
1633                 }
1634             }
1635
1636             if (listener != null) {
1637                 try {
1638                     listener.onCallTerminated(ImsCall.this, reasonInfo);
1639                 } catch (Throwable t) {
1640                     loge("callSessionTerminated :: ", t);
1641                 }
1642             }
1643         }
1644
1645         @Override
1646         public void callSessionHeld(ImsCallSession session,
1647                 ImsCallProfile profile) {
1648             if (DBG) {
1649                 log("callSessionHeld :: session=" + session + ", profile=" + profile);
1650             }
1651
1652             ImsCall.Listener listener;
1653
1654             synchronized(ImsCall.this) {
1655                 mCallProfile = profile;
1656
1657                 if (mUpdateRequest == UPDATE_HOLD_MERGE) {
1658                     mergeInternal();
1659                     return;
1660                 }
1661
1662                 mUpdateRequest = UPDATE_NONE;
1663                 listener = mListener;
1664             }
1665
1666             if (listener != null) {
1667                 try {
1668                     listener.onCallHeld(ImsCall.this);
1669                 } catch (Throwable t) {
1670                     loge("callSessionHeld :: ", t);
1671                 }
1672             }
1673         }
1674
1675         @Override
1676         public void callSessionHoldFailed(ImsCallSession session,
1677                 ImsReasonInfo reasonInfo) {
1678             if (DBG) {
1679                 log("callSessionHoldFailed :: session=" + session +
1680                         ", reasonInfo=" + reasonInfo);
1681             }
1682
1683             boolean isHoldForMerge = false;
1684             ImsCall.Listener listener;
1685
1686             synchronized(ImsCall.this) {
1687                 if (mUpdateRequest == UPDATE_HOLD_MERGE) {
1688                     isHoldForMerge = true;
1689                 }
1690
1691                 mUpdateRequest = UPDATE_NONE;
1692                 listener = mListener;
1693             }
1694
1695             if (isHoldForMerge) {
1696                 callSessionMergeFailed(session, reasonInfo);
1697                 return;
1698             }
1699
1700             if (listener != null) {
1701                 try {
1702                     listener.onCallHoldFailed(ImsCall.this, reasonInfo);
1703                 } catch (Throwable t) {
1704                     loge("callSessionHoldFailed :: ", t);
1705                 }
1706             }
1707         }
1708
1709         @Override
1710         public void callSessionHoldReceived(ImsCallSession session,
1711                 ImsCallProfile profile) {
1712             if (DBG) {
1713                 log("callSessionHoldReceived :: session=" + session + ", profile=" + profile);
1714             }
1715
1716             ImsCall.Listener listener;
1717
1718             synchronized(ImsCall.this) {
1719                 listener = mListener;
1720                 mCallProfile = profile;
1721             }
1722
1723             if (listener != null) {
1724                 try {
1725                     listener.onCallHoldReceived(ImsCall.this);
1726                 } catch (Throwable t) {
1727                     loge("callSessionHoldReceived :: ", t);
1728                 }
1729             }
1730         }
1731
1732         @Override
1733         public void callSessionResumed(ImsCallSession session,
1734                 ImsCallProfile profile) {
1735             if (DBG) {
1736                 log("callSessionResumed :: session=" + session + ", profile=" + profile);
1737             }
1738
1739             ImsCall.Listener listener;
1740
1741             synchronized(ImsCall.this) {
1742                 listener = mListener;
1743                 mCallProfile = profile;
1744                 mUpdateRequest = UPDATE_NONE;
1745             }
1746
1747             if (listener != null) {
1748                 try {
1749                     listener.onCallResumed(ImsCall.this);
1750                 } catch (Throwable t) {
1751                     loge("callSessionResumed :: ", t);
1752                 }
1753             }
1754         }
1755
1756         @Override
1757         public void callSessionResumeFailed(ImsCallSession session,
1758                 ImsReasonInfo reasonInfo) {
1759             if (DBG) {
1760                 log("callSessionResumeFailed :: session=" + session +
1761                         ", reasonInfo=" + reasonInfo);
1762             }
1763
1764             ImsCall.Listener listener;
1765
1766             synchronized(ImsCall.this) {
1767                 listener = mListener;
1768                 mUpdateRequest = UPDATE_NONE;
1769             }
1770
1771             if (listener != null) {
1772                 try {
1773                     listener.onCallResumeFailed(ImsCall.this, reasonInfo);
1774                 } catch (Throwable t) {
1775                     loge("callSessionResumeFailed :: ", t);
1776                 }
1777             }
1778         }
1779
1780         @Override
1781         public void callSessionResumeReceived(ImsCallSession session,
1782                 ImsCallProfile profile) {
1783             if (DBG) {
1784                 log("callSessionResumeReceived :: session=" + session +
1785                         ", profile=" + profile);
1786             }
1787
1788             ImsCall.Listener listener;
1789
1790             synchronized(ImsCall.this) {
1791                 listener = mListener;
1792                 mCallProfile = profile;
1793             }
1794
1795             if (listener != null) {
1796                 try {
1797                     listener.onCallResumeReceived(ImsCall.this);
1798                 } catch (Throwable t) {
1799                     loge("callSessionResumeReceived :: ", t);
1800                 }
1801             }
1802         }
1803
1804         @Override
1805         public void callSessionMerged(ImsCallSession session,
1806                 ImsCallSession newSession, ImsCallProfile profile) {
1807             if (DBG) {
1808                 log("callSessionMerged :: session=" + session
1809                         + ", newSession=" + newSession + ", profile=" + profile);
1810             }
1811
1812             ImsCall newCall = createNewCall(newSession, profile);
1813
1814             if (newCall == null) {
1815                 callSessionMergeFailed(session, new ImsReasonInfo());
1816                 return;
1817             }
1818
1819             ImsCall.Listener listener;
1820
1821             synchronized(ImsCall.this) {
1822                 listener = mListener;
1823                 updateCallGroup(newCall);
1824                 mUpdateRequest = UPDATE_NONE;
1825             }
1826
1827             if (listener != null) {
1828                 try {
1829                     listener.onCallMerged(ImsCall.this, newCall);
1830                 } catch (Throwable t) {
1831                     loge("callSessionMerged :: ", t);
1832                 }
1833             }
1834         }
1835
1836         @Override
1837         public void callSessionMergeFailed(ImsCallSession session,
1838                 ImsReasonInfo reasonInfo) {
1839             if (DBG) {
1840                 log("callSessionMergeFailed :: session=" + session +
1841                         ", reasonInfo=" + reasonInfo);
1842             }
1843
1844             ImsCall.Listener listener;
1845
1846             synchronized(ImsCall.this) {
1847                 listener = mListener;
1848                 updateCallGroup(null);
1849                 mUpdateRequest = UPDATE_NONE;
1850             }
1851
1852             if (listener != null) {
1853                 try {
1854                     listener.onCallMergeFailed(ImsCall.this, reasonInfo);
1855                 } catch (Throwable t) {
1856                     loge("callSessionMergeFailed :: ", t);
1857                 }
1858             }
1859         }
1860
1861         @Override
1862         public void callSessionUpdated(ImsCallSession session,
1863                 ImsCallProfile profile) {
1864             if (DBG) {
1865                 log("callSessionUpdated :: session=" + session + ", profile=" + profile);
1866             }
1867
1868             ImsCall.Listener listener;
1869
1870             synchronized(ImsCall.this) {
1871                 listener = mListener;
1872                 mCallProfile = profile;
1873                 mUpdateRequest = UPDATE_NONE;
1874             }
1875
1876             if (listener != null) {
1877                 try {
1878                     listener.onCallUpdated(ImsCall.this);
1879                 } catch (Throwable t) {
1880                     loge("callSessionUpdated :: ", t);
1881                 }
1882             }
1883         }
1884
1885         @Override
1886         public void callSessionUpdateFailed(ImsCallSession session,
1887                 ImsReasonInfo reasonInfo) {
1888             if (DBG) {
1889                 log("callSessionUpdateFailed :: session=" + session +
1890                         ", reasonInfo=" + reasonInfo);
1891             }
1892
1893             ImsCall.Listener listener;
1894
1895             synchronized(ImsCall.this) {
1896                 listener = mListener;
1897                 mUpdateRequest = UPDATE_NONE;
1898             }
1899
1900             if (listener != null) {
1901                 try {
1902                     listener.onCallUpdateFailed(ImsCall.this, reasonInfo);
1903                 } catch (Throwable t) {
1904                     loge("callSessionUpdateFailed :: ", t);
1905                 }
1906             }
1907         }
1908
1909         @Override
1910         public void callSessionUpdateReceived(ImsCallSession session,
1911                 ImsCallProfile profile) {
1912             if (DBG) {
1913                 log("callSessionUpdateReceived :: session=" + session +
1914                         ", profile=" + profile);
1915             }
1916
1917             ImsCall.Listener listener;
1918
1919             synchronized(ImsCall.this) {
1920                 listener = mListener;
1921                 mProposedCallProfile = profile;
1922                 mUpdateRequest = UPDATE_UNSPECIFIED;
1923             }
1924
1925             if (listener != null) {
1926                 try {
1927                     listener.onCallUpdateReceived(ImsCall.this);
1928                 } catch (Throwable t) {
1929                     loge("callSessionUpdateReceived :: ", t);
1930                 }
1931             }
1932         }
1933
1934         @Override
1935         public void callSessionConferenceExtended(ImsCallSession session,
1936                 ImsCallSession newSession, ImsCallProfile profile) {
1937             if (DBG) {
1938                 log("callSessionConferenceExtended :: session=" + session
1939                         + ", newSession=" + newSession + ", profile=" + profile);
1940             }
1941
1942             ImsCall newCall = createNewCall(newSession, profile);
1943
1944             if (newCall == null) {
1945                 callSessionConferenceExtendFailed(session, new ImsReasonInfo());
1946                 return;
1947             }
1948
1949             ImsCall.Listener listener;
1950
1951             synchronized(ImsCall.this) {
1952                 listener = mListener;
1953                 mUpdateRequest = UPDATE_NONE;
1954             }
1955
1956             if (listener != null) {
1957                 try {
1958                     listener.onCallConferenceExtended(ImsCall.this, newCall);
1959                 } catch (Throwable t) {
1960                     loge("callSessionConferenceExtended :: ", t);
1961                 }
1962             }
1963         }
1964
1965         @Override
1966         public void callSessionConferenceExtendFailed(ImsCallSession session,
1967                 ImsReasonInfo reasonInfo) {
1968             if (DBG) {
1969                 log("callSessionConferenceExtendFailed :: session=" + session +
1970                         ", reasonInfo=" + reasonInfo);
1971             }
1972
1973             ImsCall.Listener listener;
1974
1975             synchronized(ImsCall.this) {
1976                 listener = mListener;
1977                 mUpdateRequest = UPDATE_NONE;
1978             }
1979
1980             if (listener != null) {
1981                 try {
1982                     listener.onCallConferenceExtendFailed(ImsCall.this, reasonInfo);
1983                 } catch (Throwable t) {
1984                     loge("callSessionConferenceExtendFailed :: ", t);
1985                 }
1986             }
1987         }
1988
1989         @Override
1990         public void callSessionConferenceExtendReceived(ImsCallSession session,
1991                 ImsCallSession newSession, ImsCallProfile profile) {
1992             if (DBG) {
1993                 log("callSessionConferenceExtendReceived :: session=" + session
1994                         + ", newSession=" + newSession + ", profile=" + profile);
1995             }
1996
1997             ImsCall newCall = createNewCall(newSession, profile);
1998
1999             if (newCall == null) {
2000                 // Should all the calls be terminated...???
2001                 return;
2002             }
2003
2004             ImsCall.Listener listener;
2005
2006             synchronized(ImsCall.this) {
2007                 listener = mListener;
2008             }
2009
2010             if (listener != null) {
2011                 try {
2012                     listener.onCallConferenceExtendReceived(ImsCall.this, newCall);
2013                 } catch (Throwable t) {
2014                     loge("callSessionConferenceExtendReceived :: ", t);
2015                 }
2016             }
2017         }
2018
2019         @Override
2020         public void callSessionInviteParticipantsRequestDelivered(ImsCallSession session) {
2021             if (DBG) {
2022                 log("callSessionInviteParticipantsRequestDelivered :: session=" + session);
2023             }
2024
2025             ImsCall.Listener listener;
2026
2027             synchronized(ImsCall.this) {
2028                 listener = mListener;
2029             }
2030
2031             if (listener != null) {
2032                 try {
2033                     listener.onCallInviteParticipantsRequestDelivered(ImsCall.this);
2034                 } catch (Throwable t) {
2035                     loge("callSessionInviteParticipantsRequestDelivered :: ", t);
2036                 }
2037             }
2038         }
2039
2040         @Override
2041         public void callSessionInviteParticipantsRequestFailed(ImsCallSession session,
2042                 ImsReasonInfo reasonInfo) {
2043             if (DBG) {
2044                 log("callSessionInviteParticipantsRequestFailed :: session=" + session
2045                         + ", reasonInfo=" + reasonInfo);
2046             }
2047
2048             ImsCall.Listener listener;
2049
2050             synchronized(ImsCall.this) {
2051                 listener = mListener;
2052             }
2053
2054             if (listener != null) {
2055                 try {
2056                     listener.onCallInviteParticipantsRequestFailed(ImsCall.this, reasonInfo);
2057                 } catch (Throwable t) {
2058                     loge("callSessionInviteParticipantsRequestFailed :: ", t);
2059                 }
2060             }
2061         }
2062
2063         @Override
2064         public void callSessionRemoveParticipantsRequestDelivered(ImsCallSession session) {
2065             if (DBG) {
2066                 log("callSessionRemoveParticipantsRequestDelivered :: session=" + session);
2067             }
2068
2069             ImsCall.Listener listener;
2070
2071             synchronized(ImsCall.this) {
2072                 listener = mListener;
2073             }
2074
2075             if (listener != null) {
2076                 try {
2077                     listener.onCallRemoveParticipantsRequestDelivered(ImsCall.this);
2078                 } catch (Throwable t) {
2079                     loge("callSessionRemoveParticipantsRequestDelivered :: ", t);
2080                 }
2081             }
2082         }
2083
2084         @Override
2085         public void callSessionRemoveParticipantsRequestFailed(ImsCallSession session,
2086                 ImsReasonInfo reasonInfo) {
2087             if (DBG) {
2088                 log("callSessionRemoveParticipantsRequestFailed :: session=" + session
2089                         + ", reasonInfo=" + reasonInfo);
2090             }
2091
2092             ImsCall.Listener listener;
2093
2094             synchronized(ImsCall.this) {
2095                 listener = mListener;
2096             }
2097
2098             if (listener != null) {
2099                 try {
2100                     listener.onCallRemoveParticipantsRequestFailed(ImsCall.this, reasonInfo);
2101                 } catch (Throwable t) {
2102                     loge("callSessionRemoveParticipantsRequestFailed :: ", t);
2103                 }
2104             }
2105         }
2106
2107         @Override
2108         public void callSessionConferenceStateUpdated(ImsCallSession session,
2109                 ImsConferenceState state) {
2110             if (DBG) {
2111                 log("callSessionConferenceStateUpdated :: session=" + session
2112                         + ", state=" + state);
2113             }
2114
2115             ImsCall.Listener listener;
2116
2117             synchronized(ImsCall.this) {
2118                 notifyConferenceStateUpdated(state);
2119                 listener = mListener;
2120             }
2121
2122             if (listener != null) {
2123                 try {
2124                     listener.onCallConferenceStateUpdated(ImsCall.this, state);
2125                 } catch (Throwable t) {
2126                     loge("callSessionConferenceStateUpdated :: ", t);
2127                 }
2128             }
2129         }
2130
2131         @Override
2132         public void callSessionUssdMessageReceived(ImsCallSession session,
2133                 int mode, String ussdMessage) {
2134             if (DBG) {
2135                 log("callSessionUssdMessageReceived :: session=" + session
2136                         + ", mode=" + mode + ", ussdMessage=" + ussdMessage);
2137             }
2138
2139             ImsCall.Listener listener;
2140
2141             synchronized(ImsCall.this) {
2142                 listener = mListener;
2143             }
2144
2145             if (listener != null) {
2146                 try {
2147                     listener.onCallUssdMessageReceived(ImsCall.this, mode, ussdMessage);
2148                 } catch (Throwable t) {
2149                     loge("callSessionUssdMessageReceived :: ", t);
2150                 }
2151             }
2152         }
2153     }
2154 }