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