Skip hold request for Conference operation
[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      * @throws ImsException if the IMS service fails to accept the call
814      */
815     public void accept() throws ImsException {
816         if (DBG) {
817             log("accept :: session=" + mSession);
818         }
819
820         accept(ImsCallProfile.CALL_TYPE_VOICE, new ImsStreamMediaProfile());
821     }
822
823     /**
824      * Accepts a call.
825      *
826      * @param callType call type to be answered in {@link ImsCallProfile}
827      * @param profile a media profile to be answered (audio/audio & video, direction, ...)
828      * @see Listener#onCallStarted
829      * @throws ImsException if the IMS service fails to accept the call
830      */
831     public void accept(int callType, ImsStreamMediaProfile profile) throws ImsException {
832         if (DBG) {
833             log("accept :: session=" + mSession
834                     + ", callType=" + callType + ", profile=" + profile);
835         }
836
837         synchronized(mLockObj) {
838             if (mSession == null) {
839                 throw new ImsException("No call to answer",
840                         ImsReasonInfo.CODE_LOCAL_CALL_TERMINATED);
841             }
842
843             try {
844                 mSession.accept(callType, profile);
845             } catch (Throwable t) {
846                 loge("accept :: ", t);
847                 throw new ImsException("accept()", t, 0);
848             }
849
850             if (mInCall && (mProposedCallProfile != null)) {
851                 if (DBG) {
852                     log("accept :: call profile will be updated");
853                 }
854
855                 mCallProfile = mProposedCallProfile;
856                 mProposedCallProfile = null;
857             }
858
859             // Other call update received
860             if (mInCall && (mUpdateRequest == UPDATE_UNSPECIFIED)) {
861                 mUpdateRequest = UPDATE_NONE;
862             }
863         }
864     }
865
866     /**
867      * Rejects a call.
868      *
869      * @param reason reason code to reject an incoming call
870      * @see Listener#onCallStartFailed
871      * @throws ImsException if the IMS service fails to accept the call
872      */
873     public void reject(int reason) throws ImsException {
874         if (DBG) {
875             log("reject :: session=" + mSession + ", reason=" + reason);
876         }
877
878         synchronized(mLockObj) {
879             if (mSession != null) {
880                 mSession.reject(reason);
881             }
882
883             if (mInCall && (mProposedCallProfile != null)) {
884                 if (DBG) {
885                     log("reject :: call profile is not updated; destroy it...");
886                 }
887
888                 mProposedCallProfile = null;
889             }
890
891             // Other call update received
892             if (mInCall && (mUpdateRequest == UPDATE_UNSPECIFIED)) {
893                 mUpdateRequest = UPDATE_NONE;
894             }
895         }
896     }
897
898     /**
899      * Terminates an IMS call.
900      *
901      * @param reason reason code to terminate a call
902      * @throws ImsException if the IMS service fails to terminate the call
903      */
904     public void terminate(int reason) throws ImsException {
905         if (DBG) {
906             log("terminate :: session=" + mSession + ", reason=" + reason);
907         }
908
909         synchronized(mLockObj) {
910             mHold = false;
911             mInCall = false;
912
913             if (mSession != null) {
914                 mSession.terminate(reason);
915             }
916         }
917     }
918
919     /**
920      * Puts a call on hold. When succeeds, {@link Listener#onCallHeld} is called.
921      *
922      * @see Listener#onCallHeld, Listener#onCallHoldFailed
923      * @throws ImsException if the IMS service fails to hold the call
924      */
925     public void hold() throws ImsException {
926         if (DBG) {
927             log("hold :: session=" + mSession);
928         }
929
930         if (isOnHold()) {
931             if (DBG) {
932                 log("hold :: call is already on hold");
933             }
934             return;
935         }
936
937         synchronized(mLockObj) {
938             if (mUpdateRequest != UPDATE_NONE) {
939                 loge("hold :: update is in progress; request=" + mUpdateRequest);
940                 throw new ImsException("Call update is in progress",
941                         ImsReasonInfo.CODE_LOCAL_ILLEGAL_STATE);
942             }
943
944             if (mSession == null) {
945                 loge("hold :: ");
946                 throw new ImsException("No call session",
947                         ImsReasonInfo.CODE_LOCAL_CALL_TERMINATED);
948             }
949
950             mSession.hold(createHoldMediaProfile());
951             // FIXME: update the state on the callback?
952             mHold = true;
953             mUpdateRequest = UPDATE_HOLD;
954         }
955     }
956
957     /**
958      * Continues a call that's on hold. When succeeds, {@link Listener#onCallResumed} is called.
959      *
960      * @see Listener#onCallResumed, Listener#onCallResumeFailed
961      * @throws ImsException if the IMS service fails to resume the call
962      */
963     public void resume() throws ImsException {
964         if (DBG) {
965             log("resume :: session=" + mSession);
966         }
967
968         if (!isOnHold()) {
969             if (DBG) {
970                 log("resume :: call is in conversation");
971             }
972             return;
973         }
974
975         synchronized(mLockObj) {
976             if (mUpdateRequest != UPDATE_NONE) {
977                 loge("resume :: update is in progress; request=" + mUpdateRequest);
978                 throw new ImsException("Call update is in progress",
979                         ImsReasonInfo.CODE_LOCAL_ILLEGAL_STATE);
980             }
981
982             if (mSession == null) {
983                 loge("resume :: ");
984                 throw new ImsException("No call session",
985                         ImsReasonInfo.CODE_LOCAL_CALL_TERMINATED);
986             }
987
988             mSession.resume(createResumeMediaProfile());
989             // FIXME: update the state on the callback?
990             mHold = false;
991             mUpdateRequest = UPDATE_RESUME;
992         }
993     }
994
995     /**
996      * Merges the active & hold call.
997      *
998      * @see Listener#onCallMerged, Listener#onCallMergeFailed
999      * @throws ImsException if the IMS service fails to merge the call
1000      */
1001     public void merge() throws ImsException {
1002         if (DBG) {
1003             log("merge :: session=" + mSession);
1004         }
1005
1006         synchronized(mLockObj) {
1007             if (mUpdateRequest != UPDATE_NONE) {
1008                 loge("merge :: update is in progress; request=" + mUpdateRequest);
1009                 throw new ImsException("Call update is in progress",
1010                         ImsReasonInfo.CODE_LOCAL_ILLEGAL_STATE);
1011             }
1012
1013             if (mSession == null) {
1014                 loge("merge :: ");
1015                 throw new ImsException("No call session",
1016                         ImsReasonInfo.CODE_LOCAL_CALL_TERMINATED);
1017             }
1018
1019             // if skipHoldBeforeMerge = true, IMS service implementation will
1020             // merge without explicitly holding the call.
1021             if (mHold || (mContext.getResources().getBoolean(
1022                     com.android.internal.R.bool.skipHoldBeforeMerge))) {
1023                 mSession.merge();
1024                 mUpdateRequest = UPDATE_MERGE;
1025             } else {
1026                 mSession.hold(createHoldMediaProfile());
1027                 // FIXME: ?
1028                 mHold = true;
1029                 mUpdateRequest = UPDATE_HOLD_MERGE;
1030             }
1031         }
1032     }
1033
1034     /**
1035      * Merges the active & hold call.
1036      *
1037      * @param bgCall the background (holding) call
1038      * @see Listener#onCallMerged, Listener#onCallMergeFailed
1039      * @throws ImsException if the IMS service fails to merge the call
1040      */
1041     public void merge(ImsCall bgCall) throws ImsException {
1042         if (DBG) {
1043             log("merge(1) :: session=" + mSession);
1044         }
1045
1046         if (bgCall == null) {
1047             throw new ImsException("No background call",
1048                     ImsReasonInfo.CODE_LOCAL_ILLEGAL_ARGUMENT);
1049         }
1050
1051         synchronized(mLockObj) {
1052             createCallGroup(bgCall);
1053         }
1054
1055         merge();
1056     }
1057
1058     /**
1059      * Updates the current call's properties (ex. call mode change: video upgrade / downgrade).
1060      */
1061     public void update(int callType, ImsStreamMediaProfile mediaProfile) throws ImsException {
1062         if (DBG) {
1063             log("update :: session=" + mSession);
1064         }
1065
1066         if (isOnHold()) {
1067             if (DBG) {
1068                 log("update :: call is on hold");
1069             }
1070             throw new ImsException("Not in a call to update call",
1071                     ImsReasonInfo.CODE_LOCAL_ILLEGAL_STATE);
1072         }
1073
1074         synchronized(mLockObj) {
1075             if (mUpdateRequest != UPDATE_NONE) {
1076                 if (DBG) {
1077                     log("update :: update is in progress; request=" + mUpdateRequest);
1078                 }
1079                 throw new ImsException("Call update is in progress",
1080                         ImsReasonInfo.CODE_LOCAL_ILLEGAL_STATE);
1081             }
1082
1083             if (mSession == null) {
1084                 loge("update :: ");
1085                 throw new ImsException("No call session",
1086                         ImsReasonInfo.CODE_LOCAL_CALL_TERMINATED);
1087             }
1088
1089             mSession.update(callType, mediaProfile);
1090             mUpdateRequest = UPDATE_UNSPECIFIED;
1091         }
1092     }
1093
1094     /**
1095      * Extends this call (1-to-1 call) to the conference call
1096      * inviting the specified participants to.
1097      *
1098      */
1099     public void extendToConference(String[] participants) throws ImsException {
1100         if (DBG) {
1101             log("extendToConference :: session=" + mSession);
1102         }
1103
1104         if (isOnHold()) {
1105             if (DBG) {
1106                 log("extendToConference :: call is on hold");
1107             }
1108             throw new ImsException("Not in a call to extend a call to conference",
1109                     ImsReasonInfo.CODE_LOCAL_ILLEGAL_STATE);
1110         }
1111
1112         synchronized(mLockObj) {
1113             if (mUpdateRequest != UPDATE_NONE) {
1114                 if (DBG) {
1115                     log("extendToConference :: update is in progress; request=" + mUpdateRequest);
1116                 }
1117                 throw new ImsException("Call update is in progress",
1118                         ImsReasonInfo.CODE_LOCAL_ILLEGAL_STATE);
1119             }
1120
1121             if (mSession == null) {
1122                 loge("extendToConference :: ");
1123                 throw new ImsException("No call session",
1124                         ImsReasonInfo.CODE_LOCAL_CALL_TERMINATED);
1125             }
1126
1127             mSession.extendToConference(participants);
1128             mUpdateRequest = UPDATE_EXTEND_TO_CONFERENCE;
1129         }
1130     }
1131
1132     /**
1133      * Requests the conference server to invite an additional participants to the conference.
1134      *
1135      */
1136     public void inviteParticipants(String[] participants) throws ImsException {
1137         if (DBG) {
1138             log("inviteParticipants :: session=" + mSession);
1139         }
1140
1141         synchronized(mLockObj) {
1142             if (mSession == null) {
1143                 loge("inviteParticipants :: ");
1144                 throw new ImsException("No call session",
1145                         ImsReasonInfo.CODE_LOCAL_CALL_TERMINATED);
1146             }
1147
1148             mSession.inviteParticipants(participants);
1149         }
1150     }
1151
1152     /**
1153      * Requests the conference server to remove the specified participants from the conference.
1154      *
1155      */
1156     public void removeParticipants(String[] participants) throws ImsException {
1157         if (DBG) {
1158             log("removeParticipants :: session=" + mSession);
1159         }
1160
1161         synchronized(mLockObj) {
1162             if (mSession == null) {
1163                 loge("removeParticipants :: ");
1164                 throw new ImsException("No call session",
1165                         ImsReasonInfo.CODE_LOCAL_CALL_TERMINATED);
1166             }
1167
1168             mSession.removeParticipants(participants);
1169         }
1170     }
1171
1172
1173     /**
1174      * Sends a DTMF code. According to <a href="http://tools.ietf.org/html/rfc2833">RFC 2833</a>,
1175      * event 0 ~ 9 maps to decimal value 0 ~ 9, '*' to 10, '#' to 11, event 'A' ~ 'D' to 12 ~ 15,
1176      * and event flash to 16. Currently, event flash is not supported.
1177      *
1178      * @param code the DTMF to send. Value 0 to 15 (inclusive) are valid inputs.
1179      */
1180     public void sendDtmf(int code) {
1181         sendDtmf(code, null);
1182     }
1183
1184     /**
1185      * Sends a DTMF code. According to <a href="http://tools.ietf.org/html/rfc2833">RFC 2833</a>,
1186      * event 0 ~ 9 maps to decimal value 0 ~ 9, '*' to 10, '#' to 11, event 'A' ~ 'D' to 12 ~ 15,
1187      * and event flash to 16. Currently, event flash is not supported.
1188      *
1189      * @param code the DTMF to send. Value 0 to 15 (inclusive) are valid inputs.
1190      * @param result the result message to send when done
1191      */
1192     public void sendDtmf(int code, Message result) {
1193         if (DBG) {
1194             log("sendDtmf :: session=" + mSession + ", code=" + code);
1195         }
1196
1197         synchronized(mLockObj) {
1198             if (mSession != null) {
1199                 mSession.sendDtmf(code);
1200             }
1201         }
1202
1203         if (result != null) {
1204             result.sendToTarget();
1205         }
1206     }
1207
1208     /**
1209      * Sends an USSD message.
1210      *
1211      * @param ussdMessage USSD message to send
1212      */
1213     public void sendUssd(String ussdMessage) throws ImsException {
1214         if (DBG) {
1215             log("sendUssd :: session=" + mSession + ", ussdMessage=" + ussdMessage);
1216         }
1217
1218         synchronized(mLockObj) {
1219             if (mSession == null) {
1220                 loge("sendUssd :: ");
1221                 throw new ImsException("No call session",
1222                         ImsReasonInfo.CODE_LOCAL_CALL_TERMINATED);
1223             }
1224
1225             mSession.sendUssd(ussdMessage);
1226         }
1227     }
1228
1229     private void clear(ImsReasonInfo lastReasonInfo) {
1230         mInCall = false;
1231         mHold = false;
1232         mUpdateRequest = UPDATE_NONE;
1233         mLastReasonInfo = lastReasonInfo;
1234         destroyCallGroup();
1235     }
1236
1237     private void createCallGroup(ImsCall neutralReferrer) {
1238         CallGroup referrerCallGroup = neutralReferrer.getCallGroup();
1239
1240         if (mCallGroup == null) {
1241             if (referrerCallGroup == null) {
1242                 mCallGroup = CallGroupManager.getInstance().createCallGroup(new ImsCallGroup());
1243             } else {
1244                 mCallGroup = referrerCallGroup;
1245             }
1246
1247             if (mCallGroup != null) {
1248                 mCallGroup.setNeutralReferrer(neutralReferrer);
1249             }
1250         } else {
1251             mCallGroup.setNeutralReferrer(neutralReferrer);
1252
1253             if ((referrerCallGroup != null)
1254                     && (mCallGroup != referrerCallGroup)) {
1255                 loge("fatal :: call group is mismatched; call is corrupted...");
1256             }
1257         }
1258     }
1259
1260     private void updateCallGroup(ImsCall owner) {
1261         if (mCallGroup == null) {
1262             return;
1263         }
1264
1265         ImsCall neutralReferrer = (ImsCall)mCallGroup.getNeutralReferrer();
1266
1267         mCallGroup.setNeutralReferrer(null);
1268
1269         if (owner == null) {
1270             // Maintain the call group if the current call has been merged in the past.
1271             if (!mCallGroup.hasReferrer()) {
1272                 CallGroupManager.getInstance().destroyCallGroup(mCallGroup);
1273                 mCallGroup = null;
1274             }
1275         } else {
1276             mCallGroup.addReferrer(this);
1277
1278             if (neutralReferrer != null) {
1279                 if (neutralReferrer.isInCall() && (neutralReferrer.getCallGroup() == null)) {
1280                     neutralReferrer.setCallGroup(mCallGroup);
1281                     mCallGroup.addReferrer(neutralReferrer);
1282                 }
1283
1284                 neutralReferrer.enforceConversationMode();
1285             }
1286
1287             // Close the existing owner call if present
1288             ImsCall exOwner = (ImsCall)mCallGroup.getOwner();
1289
1290             mCallGroup.setOwner(owner);
1291
1292             if (exOwner != null) {
1293                 exOwner.close();
1294             }
1295         }
1296     }
1297
1298     private void destroyCallGroup() {
1299         if (mCallGroup == null) {
1300             return;
1301         }
1302
1303         mCallGroup.removeReferrer(this);
1304
1305         if (!mCallGroup.hasReferrer()) {
1306             CallGroupManager.getInstance().destroyCallGroup(mCallGroup);
1307         }
1308
1309         mCallGroup = null;
1310     }
1311
1312     private CallGroup getCallGroup() {
1313         synchronized(mLockObj) {
1314             return mCallGroup;
1315         }
1316     }
1317
1318     private void setCallGroup(CallGroup callGroup) {
1319         synchronized(mLockObj) {
1320             mCallGroup = callGroup;
1321         }
1322     }
1323
1324     /**
1325      * Creates an IMS call session listener.
1326      */
1327     private ImsCallSession.Listener createCallSessionListener() {
1328         return new ImsCallSessionListenerProxy();
1329     }
1330
1331     private ImsCall createNewCall(ImsCallSession session, ImsCallProfile profile) {
1332         ImsCall call = new ImsCall(mContext, profile);
1333
1334         try {
1335             call.attachSession(session);
1336         } catch (ImsException e) {
1337             if (call != null) {
1338                 call.close();
1339                 call = null;
1340             }
1341         }
1342
1343         // Do additional operations...
1344
1345         return call;
1346     }
1347
1348     private ImsStreamMediaProfile createHoldMediaProfile() {
1349         ImsStreamMediaProfile mediaProfile = new ImsStreamMediaProfile();
1350
1351         if (mCallProfile == null) {
1352             return mediaProfile;
1353         }
1354
1355         mediaProfile.mAudioQuality = mCallProfile.mMediaProfile.mAudioQuality;
1356         mediaProfile.mVideoQuality = mCallProfile.mMediaProfile.mVideoQuality;
1357         mediaProfile.mAudioDirection = ImsStreamMediaProfile.DIRECTION_SEND;
1358
1359         if (mediaProfile.mVideoQuality != ImsStreamMediaProfile.VIDEO_QUALITY_NONE) {
1360             mediaProfile.mVideoDirection = ImsStreamMediaProfile.DIRECTION_SEND;
1361         }
1362
1363         return mediaProfile;
1364     }
1365
1366     private ImsStreamMediaProfile createResumeMediaProfile() {
1367         ImsStreamMediaProfile mediaProfile = new ImsStreamMediaProfile();
1368
1369         if (mCallProfile == null) {
1370             return mediaProfile;
1371         }
1372
1373         mediaProfile.mAudioQuality = mCallProfile.mMediaProfile.mAudioQuality;
1374         mediaProfile.mVideoQuality = mCallProfile.mMediaProfile.mVideoQuality;
1375         mediaProfile.mAudioDirection = ImsStreamMediaProfile.DIRECTION_SEND_RECEIVE;
1376
1377         if (mediaProfile.mVideoQuality != ImsStreamMediaProfile.VIDEO_QUALITY_NONE) {
1378             mediaProfile.mVideoDirection = ImsStreamMediaProfile.DIRECTION_SEND_RECEIVE;
1379         }
1380
1381         return mediaProfile;
1382     }
1383
1384     private void enforceConversationMode() {
1385         if (mInCall) {
1386             mHold = false;
1387             mUpdateRequest = UPDATE_NONE;
1388         }
1389     }
1390
1391     private void mergeInternal() {
1392         if (DBG) {
1393             log("mergeInternal :: session=" + mSession);
1394         }
1395
1396         mSession.merge();
1397         mUpdateRequest = UPDATE_MERGE;
1398     }
1399
1400     private void notifyCallStateChanged() {
1401         int state = 0;
1402
1403         if (mInCall && (mUpdateRequest == UPDATE_HOLD_MERGE)) {
1404             state = CALL_STATE_ACTIVE_TO_HOLD;
1405             mHold = true;
1406         } else if (mInCall && ((mUpdateRequest == UPDATE_MERGE)
1407                 || (mUpdateRequest == UPDATE_EXTEND_TO_CONFERENCE))) {
1408             state = CALL_STATE_HOLD_TO_ACTIVE;
1409             mHold = false;
1410             mMute = false;
1411         }
1412
1413         if (state != 0) {
1414             if (mListener != null) {
1415                 try {
1416                     mListener.onCallStateChanged(ImsCall.this, state);
1417                 } catch (Throwable t) {
1418                     loge("notifyCallStateChanged :: ", t);
1419                 }
1420             }
1421         }
1422     }
1423
1424     private void notifyConferenceSessionTerminated(ImsReasonInfo reasonInfo) {
1425         ImsCall.Listener listener;
1426
1427         if (mCallGroup.isOwner(ImsCall.this)) {
1428             ArrayList<ICall> referrers = mCallGroup.getReferrers();
1429
1430             if (referrers != null) {
1431                 for (int i = 0; i < referrers.size(); ++i) {
1432                     ImsCall call = (ImsCall)referrers.get(i);
1433
1434                     if (call == null) {
1435                         continue;
1436                     }
1437
1438                     listener = call.mListener;
1439                     call.clear(reasonInfo);
1440
1441                     if (listener != null) {
1442                         try {
1443                             listener.onCallTerminated(call, reasonInfo);
1444                         } catch (Throwable t) {
1445                             loge("notifyConferenceSessionTerminated :: ", t);
1446                         }
1447                     }
1448                 }
1449             }
1450         } else if (!mCallGroup.isReferrer(ImsCall.this)) {
1451             return;
1452         }
1453
1454         listener = mListener;
1455         clear(reasonInfo);
1456
1457         if (listener != null) {
1458             try {
1459                 listener.onCallTerminated(this, reasonInfo);
1460             } catch (Throwable t) {
1461                 loge("notifyConferenceSessionTerminated :: ", t);
1462             }
1463         }
1464     }
1465
1466     private void notifyConferenceStateUpdated(ImsConferenceState state) {
1467         Set<Entry<String, Bundle>> paticipants = state.mParticipants.entrySet();
1468
1469         if (paticipants == null) {
1470             return;
1471         }
1472
1473         Iterator<Entry<String, Bundle>> iterator = paticipants.iterator();
1474
1475         while (iterator.hasNext()) {
1476             Entry<String, Bundle> entry = iterator.next();
1477
1478             String key = entry.getKey();
1479             Bundle confInfo = entry.getValue();
1480             String status = confInfo.getString(ImsConferenceState.STATUS);
1481             String user = confInfo.getString(ImsConferenceState.USER);
1482             String endpoint = confInfo.getString(ImsConferenceState.ENDPOINT);
1483
1484             if (DBG) {
1485                 log("notifyConferenceStateUpdated :: key=" + key +
1486                         ", status=" + status +
1487                         ", user=" + user +
1488                         ", endpoint=" + endpoint);
1489             }
1490
1491             if ((mCallGroup != null) && (!mCallGroup.isOwner(ImsCall.this))) {
1492                 continue;
1493             }
1494
1495             ImsCall referrer = (ImsCall)mCallGroup.getReferrer(endpoint);
1496
1497             if (referrer == null) {
1498                 continue;
1499             }
1500
1501             if (referrer.mListener == null) {
1502                 continue;
1503             }
1504
1505             try {
1506                 if (status.equals(ImsConferenceState.STATUS_ALERTING)) {
1507                     referrer.mListener.onCallProgressing(referrer);
1508                 }
1509                 else if (status.equals(ImsConferenceState.STATUS_CONNECT_FAIL)) {
1510                     referrer.mListener.onCallStartFailed(referrer, new ImsReasonInfo());
1511                 }
1512                 else if (status.equals(ImsConferenceState.STATUS_ON_HOLD)) {
1513                     referrer.mListener.onCallHoldReceived(referrer);
1514                 }
1515                 else if (status.equals(ImsConferenceState.STATUS_CONNECTED)) {
1516                     referrer.mListener.onCallStarted(referrer);
1517                 }
1518                 else if (status.equals(ImsConferenceState.STATUS_DISCONNECTED)) {
1519                     referrer.clear(new ImsReasonInfo());
1520                     referrer.mListener.onCallTerminated(referrer, referrer.mLastReasonInfo);
1521                 }
1522             } catch (Throwable t) {
1523                 loge("notifyConferenceStateUpdated :: ", t);
1524             }
1525         }
1526     }
1527
1528     private void notifyError(int reason, int statusCode, String message) {
1529     }
1530
1531     private void throwImsException(Throwable t, int code) throws ImsException {
1532         if (t instanceof ImsException) {
1533             throw (ImsException) t;
1534         } else {
1535             throw new ImsException(String.valueOf(code), t, code);
1536         }
1537     }
1538
1539     private void log(String s) {
1540         Rlog.d(TAG, s);
1541     }
1542
1543     private void loge(String s) {
1544         Rlog.e(TAG, s);
1545     }
1546
1547     private void loge(String s, Throwable t) {
1548         Rlog.e(TAG, s, t);
1549     }
1550
1551     private class ImsCallSessionListenerProxy extends ImsCallSession.Listener {
1552         @Override
1553         public void callSessionProgressing(ImsCallSession session,
1554                 ImsStreamMediaProfile profile) {
1555             if (DBG) {
1556                 log("callSessionProgressing :: session=" + session + ", profile=" + profile);
1557             }
1558
1559             ImsCall.Listener listener;
1560
1561             synchronized(ImsCall.this) {
1562                 listener = mListener;
1563                 mCallProfile.mMediaProfile.copyFrom(profile);
1564             }
1565
1566             if (listener != null) {
1567                 try {
1568                     listener.onCallProgressing(ImsCall.this);
1569                 } catch (Throwable t) {
1570                     loge("callSessionProgressing :: ", t);
1571                 }
1572             }
1573         }
1574
1575         @Override
1576         public void callSessionStarted(ImsCallSession session,
1577                 ImsCallProfile profile) {
1578             if (DBG) {
1579                 log("callSessionStarted :: session=" + session + ", profile=" + profile);
1580             }
1581
1582             ImsCall.Listener listener;
1583
1584             synchronized(ImsCall.this) {
1585                 listener = mListener;
1586                 mCallProfile = profile;
1587             }
1588
1589             if (listener != null) {
1590                 try {
1591                     listener.onCallStarted(ImsCall.this);
1592                 } catch (Throwable t) {
1593                     loge("callSessionStarted :: ", t);
1594                 }
1595             }
1596         }
1597
1598         @Override
1599         public void callSessionStartFailed(ImsCallSession session,
1600                 ImsReasonInfo reasonInfo) {
1601             if (DBG) {
1602                 log("callSessionStartFailed :: session=" + session +
1603                         ", reasonInfo=" + reasonInfo);
1604             }
1605
1606             ImsCall.Listener listener;
1607
1608             synchronized(ImsCall.this) {
1609                 listener = mListener;
1610                 mLastReasonInfo = reasonInfo;
1611             }
1612
1613             if (listener != null) {
1614                 try {
1615                     listener.onCallStartFailed(ImsCall.this, reasonInfo);
1616                 } catch (Throwable t) {
1617                     loge("callSessionStarted :: ", t);
1618                 }
1619             }
1620         }
1621
1622         @Override
1623         public void callSessionTerminated(ImsCallSession session,
1624                 ImsReasonInfo reasonInfo) {
1625             if (DBG) {
1626                 log("callSessionTerminated :: session=" + session +
1627                         ", reasonInfo=" + reasonInfo);
1628             }
1629
1630             ImsCall.Listener listener = null;
1631
1632             synchronized(ImsCall.this) {
1633                 if (mCallGroup != null) {
1634                     notifyConferenceSessionTerminated(reasonInfo);
1635                 } else {
1636                     listener = mListener;
1637                     clear(reasonInfo);
1638                 }
1639             }
1640
1641             if (listener != null) {
1642                 try {
1643                     listener.onCallTerminated(ImsCall.this, reasonInfo);
1644                 } catch (Throwable t) {
1645                     loge("callSessionTerminated :: ", t);
1646                 }
1647             }
1648         }
1649
1650         @Override
1651         public void callSessionHeld(ImsCallSession session,
1652                 ImsCallProfile profile) {
1653             if (DBG) {
1654                 log("callSessionHeld :: session=" + session + ", profile=" + profile);
1655             }
1656
1657             ImsCall.Listener listener;
1658
1659             synchronized(ImsCall.this) {
1660                 mCallProfile = profile;
1661
1662                 if (mUpdateRequest == UPDATE_HOLD_MERGE) {
1663                     mergeInternal();
1664                     return;
1665                 }
1666
1667                 mUpdateRequest = UPDATE_NONE;
1668                 listener = mListener;
1669             }
1670
1671             if (listener != null) {
1672                 try {
1673                     listener.onCallHeld(ImsCall.this);
1674                 } catch (Throwable t) {
1675                     loge("callSessionHeld :: ", t);
1676                 }
1677             }
1678         }
1679
1680         @Override
1681         public void callSessionHoldFailed(ImsCallSession session,
1682                 ImsReasonInfo reasonInfo) {
1683             if (DBG) {
1684                 log("callSessionHoldFailed :: session=" + session +
1685                         ", reasonInfo=" + reasonInfo);
1686             }
1687
1688             boolean isHoldForMerge = false;
1689             ImsCall.Listener listener;
1690
1691             synchronized(ImsCall.this) {
1692                 if (mUpdateRequest == UPDATE_HOLD_MERGE) {
1693                     isHoldForMerge = true;
1694                 }
1695
1696                 mUpdateRequest = UPDATE_NONE;
1697                 listener = mListener;
1698             }
1699
1700             if (isHoldForMerge) {
1701                 callSessionMergeFailed(session, reasonInfo);
1702                 return;
1703             }
1704
1705             if (listener != null) {
1706                 try {
1707                     listener.onCallHoldFailed(ImsCall.this, reasonInfo);
1708                 } catch (Throwable t) {
1709                     loge("callSessionHoldFailed :: ", t);
1710                 }
1711             }
1712         }
1713
1714         @Override
1715         public void callSessionHoldReceived(ImsCallSession session,
1716                 ImsCallProfile profile) {
1717             if (DBG) {
1718                 log("callSessionHoldReceived :: session=" + session + ", profile=" + profile);
1719             }
1720
1721             ImsCall.Listener listener;
1722
1723             synchronized(ImsCall.this) {
1724                 listener = mListener;
1725                 mCallProfile = profile;
1726             }
1727
1728             if (listener != null) {
1729                 try {
1730                     listener.onCallHoldReceived(ImsCall.this);
1731                 } catch (Throwable t) {
1732                     loge("callSessionHoldReceived :: ", t);
1733                 }
1734             }
1735         }
1736
1737         @Override
1738         public void callSessionResumed(ImsCallSession session,
1739                 ImsCallProfile profile) {
1740             if (DBG) {
1741                 log("callSessionResumed :: session=" + session + ", profile=" + profile);
1742             }
1743
1744             ImsCall.Listener listener;
1745
1746             synchronized(ImsCall.this) {
1747                 listener = mListener;
1748                 mCallProfile = profile;
1749                 mUpdateRequest = UPDATE_NONE;
1750             }
1751
1752             if (listener != null) {
1753                 try {
1754                     listener.onCallResumed(ImsCall.this);
1755                 } catch (Throwable t) {
1756                     loge("callSessionResumed :: ", t);
1757                 }
1758             }
1759         }
1760
1761         @Override
1762         public void callSessionResumeFailed(ImsCallSession session,
1763                 ImsReasonInfo reasonInfo) {
1764             if (DBG) {
1765                 log("callSessionResumeFailed :: session=" + session +
1766                         ", reasonInfo=" + reasonInfo);
1767             }
1768
1769             ImsCall.Listener listener;
1770
1771             synchronized(ImsCall.this) {
1772                 listener = mListener;
1773                 mUpdateRequest = UPDATE_NONE;
1774             }
1775
1776             if (listener != null) {
1777                 try {
1778                     listener.onCallResumeFailed(ImsCall.this, reasonInfo);
1779                 } catch (Throwable t) {
1780                     loge("callSessionResumeFailed :: ", t);
1781                 }
1782             }
1783         }
1784
1785         @Override
1786         public void callSessionResumeReceived(ImsCallSession session,
1787                 ImsCallProfile profile) {
1788             if (DBG) {
1789                 log("callSessionResumeReceived :: session=" + session +
1790                         ", profile=" + profile);
1791             }
1792
1793             ImsCall.Listener listener;
1794
1795             synchronized(ImsCall.this) {
1796                 listener = mListener;
1797                 mCallProfile = profile;
1798             }
1799
1800             if (listener != null) {
1801                 try {
1802                     listener.onCallResumeReceived(ImsCall.this);
1803                 } catch (Throwable t) {
1804                     loge("callSessionResumeReceived :: ", t);
1805                 }
1806             }
1807         }
1808
1809         @Override
1810         public void callSessionMerged(ImsCallSession session,
1811                 ImsCallSession newSession, ImsCallProfile profile) {
1812             if (DBG) {
1813                 log("callSessionMerged :: session=" + session
1814                         + ", newSession=" + newSession + ", profile=" + profile);
1815             }
1816
1817             ImsCall newCall = createNewCall(newSession, profile);
1818
1819             if (newCall == null) {
1820                 callSessionMergeFailed(session, new ImsReasonInfo());
1821                 return;
1822             }
1823
1824             ImsCall.Listener listener;
1825
1826             synchronized(ImsCall.this) {
1827                 listener = mListener;
1828                 updateCallGroup(newCall);
1829                 mUpdateRequest = UPDATE_NONE;
1830             }
1831
1832             if (listener != null) {
1833                 try {
1834                     listener.onCallMerged(ImsCall.this, newCall);
1835                 } catch (Throwable t) {
1836                     loge("callSessionMerged :: ", t);
1837                 }
1838             }
1839         }
1840
1841         @Override
1842         public void callSessionMergeFailed(ImsCallSession session,
1843                 ImsReasonInfo reasonInfo) {
1844             if (DBG) {
1845                 log("callSessionMergeFailed :: session=" + session +
1846                         ", reasonInfo=" + reasonInfo);
1847             }
1848
1849             ImsCall.Listener listener;
1850
1851             synchronized(ImsCall.this) {
1852                 listener = mListener;
1853                 updateCallGroup(null);
1854                 mUpdateRequest = UPDATE_NONE;
1855             }
1856
1857             if (listener != null) {
1858                 try {
1859                     listener.onCallMergeFailed(ImsCall.this, reasonInfo);
1860                 } catch (Throwable t) {
1861                     loge("callSessionMergeFailed :: ", t);
1862                 }
1863             }
1864         }
1865
1866         @Override
1867         public void callSessionUpdated(ImsCallSession session,
1868                 ImsCallProfile profile) {
1869             if (DBG) {
1870                 log("callSessionUpdated :: session=" + session + ", profile=" + profile);
1871             }
1872
1873             ImsCall.Listener listener;
1874
1875             synchronized(ImsCall.this) {
1876                 listener = mListener;
1877                 mCallProfile = profile;
1878                 mUpdateRequest = UPDATE_NONE;
1879             }
1880
1881             if (listener != null) {
1882                 try {
1883                     listener.onCallUpdated(ImsCall.this);
1884                 } catch (Throwable t) {
1885                     loge("callSessionUpdated :: ", t);
1886                 }
1887             }
1888         }
1889
1890         @Override
1891         public void callSessionUpdateFailed(ImsCallSession session,
1892                 ImsReasonInfo reasonInfo) {
1893             if (DBG) {
1894                 log("callSessionUpdateFailed :: session=" + session +
1895                         ", reasonInfo=" + reasonInfo);
1896             }
1897
1898             ImsCall.Listener listener;
1899
1900             synchronized(ImsCall.this) {
1901                 listener = mListener;
1902                 mUpdateRequest = UPDATE_NONE;
1903             }
1904
1905             if (listener != null) {
1906                 try {
1907                     listener.onCallUpdateFailed(ImsCall.this, reasonInfo);
1908                 } catch (Throwable t) {
1909                     loge("callSessionUpdateFailed :: ", t);
1910                 }
1911             }
1912         }
1913
1914         @Override
1915         public void callSessionUpdateReceived(ImsCallSession session,
1916                 ImsCallProfile profile) {
1917             if (DBG) {
1918                 log("callSessionUpdateReceived :: session=" + session +
1919                         ", profile=" + profile);
1920             }
1921
1922             ImsCall.Listener listener;
1923
1924             synchronized(ImsCall.this) {
1925                 listener = mListener;
1926                 mProposedCallProfile = profile;
1927                 mUpdateRequest = UPDATE_UNSPECIFIED;
1928             }
1929
1930             if (listener != null) {
1931                 try {
1932                     listener.onCallUpdateReceived(ImsCall.this);
1933                 } catch (Throwable t) {
1934                     loge("callSessionUpdateReceived :: ", t);
1935                 }
1936             }
1937         }
1938
1939         @Override
1940         public void callSessionConferenceExtended(ImsCallSession session,
1941                 ImsCallSession newSession, ImsCallProfile profile) {
1942             if (DBG) {
1943                 log("callSessionConferenceExtended :: session=" + session
1944                         + ", newSession=" + newSession + ", profile=" + profile);
1945             }
1946
1947             ImsCall newCall = createNewCall(newSession, profile);
1948
1949             if (newCall == null) {
1950                 callSessionConferenceExtendFailed(session, new ImsReasonInfo());
1951                 return;
1952             }
1953
1954             ImsCall.Listener listener;
1955
1956             synchronized(ImsCall.this) {
1957                 listener = mListener;
1958                 mUpdateRequest = UPDATE_NONE;
1959             }
1960
1961             if (listener != null) {
1962                 try {
1963                     listener.onCallConferenceExtended(ImsCall.this, newCall);
1964                 } catch (Throwable t) {
1965                     loge("callSessionConferenceExtended :: ", t);
1966                 }
1967             }
1968         }
1969
1970         @Override
1971         public void callSessionConferenceExtendFailed(ImsCallSession session,
1972                 ImsReasonInfo reasonInfo) {
1973             if (DBG) {
1974                 log("callSessionConferenceExtendFailed :: session=" + session +
1975                         ", reasonInfo=" + reasonInfo);
1976             }
1977
1978             ImsCall.Listener listener;
1979
1980             synchronized(ImsCall.this) {
1981                 listener = mListener;
1982                 mUpdateRequest = UPDATE_NONE;
1983             }
1984
1985             if (listener != null) {
1986                 try {
1987                     listener.onCallConferenceExtendFailed(ImsCall.this, reasonInfo);
1988                 } catch (Throwable t) {
1989                     loge("callSessionConferenceExtendFailed :: ", t);
1990                 }
1991             }
1992         }
1993
1994         @Override
1995         public void callSessionConferenceExtendReceived(ImsCallSession session,
1996                 ImsCallSession newSession, ImsCallProfile profile) {
1997             if (DBG) {
1998                 log("callSessionConferenceExtendReceived :: session=" + session
1999                         + ", newSession=" + newSession + ", profile=" + profile);
2000             }
2001
2002             ImsCall newCall = createNewCall(newSession, profile);
2003
2004             if (newCall == null) {
2005                 // Should all the calls be terminated...???
2006                 return;
2007             }
2008
2009             ImsCall.Listener listener;
2010
2011             synchronized(ImsCall.this) {
2012                 listener = mListener;
2013             }
2014
2015             if (listener != null) {
2016                 try {
2017                     listener.onCallConferenceExtendReceived(ImsCall.this, newCall);
2018                 } catch (Throwable t) {
2019                     loge("callSessionConferenceExtendReceived :: ", t);
2020                 }
2021             }
2022         }
2023
2024         @Override
2025         public void callSessionInviteParticipantsRequestDelivered(ImsCallSession session) {
2026             if (DBG) {
2027                 log("callSessionInviteParticipantsRequestDelivered :: session=" + session);
2028             }
2029
2030             ImsCall.Listener listener;
2031
2032             synchronized(ImsCall.this) {
2033                 listener = mListener;
2034             }
2035
2036             if (listener != null) {
2037                 try {
2038                     listener.onCallInviteParticipantsRequestDelivered(ImsCall.this);
2039                 } catch (Throwable t) {
2040                     loge("callSessionInviteParticipantsRequestDelivered :: ", t);
2041                 }
2042             }
2043         }
2044
2045         @Override
2046         public void callSessionInviteParticipantsRequestFailed(ImsCallSession session,
2047                 ImsReasonInfo reasonInfo) {
2048             if (DBG) {
2049                 log("callSessionInviteParticipantsRequestFailed :: session=" + session
2050                         + ", reasonInfo=" + reasonInfo);
2051             }
2052
2053             ImsCall.Listener listener;
2054
2055             synchronized(ImsCall.this) {
2056                 listener = mListener;
2057             }
2058
2059             if (listener != null) {
2060                 try {
2061                     listener.onCallInviteParticipantsRequestFailed(ImsCall.this, reasonInfo);
2062                 } catch (Throwable t) {
2063                     loge("callSessionInviteParticipantsRequestFailed :: ", t);
2064                 }
2065             }
2066         }
2067
2068         @Override
2069         public void callSessionRemoveParticipantsRequestDelivered(ImsCallSession session) {
2070             if (DBG) {
2071                 log("callSessionRemoveParticipantsRequestDelivered :: session=" + session);
2072             }
2073
2074             ImsCall.Listener listener;
2075
2076             synchronized(ImsCall.this) {
2077                 listener = mListener;
2078             }
2079
2080             if (listener != null) {
2081                 try {
2082                     listener.onCallRemoveParticipantsRequestDelivered(ImsCall.this);
2083                 } catch (Throwable t) {
2084                     loge("callSessionRemoveParticipantsRequestDelivered :: ", t);
2085                 }
2086             }
2087         }
2088
2089         @Override
2090         public void callSessionRemoveParticipantsRequestFailed(ImsCallSession session,
2091                 ImsReasonInfo reasonInfo) {
2092             if (DBG) {
2093                 log("callSessionRemoveParticipantsRequestFailed :: session=" + session
2094                         + ", reasonInfo=" + reasonInfo);
2095             }
2096
2097             ImsCall.Listener listener;
2098
2099             synchronized(ImsCall.this) {
2100                 listener = mListener;
2101             }
2102
2103             if (listener != null) {
2104                 try {
2105                     listener.onCallRemoveParticipantsRequestFailed(ImsCall.this, reasonInfo);
2106                 } catch (Throwable t) {
2107                     loge("callSessionRemoveParticipantsRequestFailed :: ", t);
2108                 }
2109             }
2110         }
2111
2112         @Override
2113         public void callSessionConferenceStateUpdated(ImsCallSession session,
2114                 ImsConferenceState state) {
2115             if (DBG) {
2116                 log("callSessionConferenceStateUpdated :: session=" + session
2117                         + ", state=" + state);
2118             }
2119
2120             ImsCall.Listener listener;
2121
2122             synchronized(ImsCall.this) {
2123                 notifyConferenceStateUpdated(state);
2124                 listener = mListener;
2125             }
2126
2127             if (listener != null) {
2128                 try {
2129                     listener.onCallConferenceStateUpdated(ImsCall.this, state);
2130                 } catch (Throwable t) {
2131                     loge("callSessionConferenceStateUpdated :: ", t);
2132                 }
2133             }
2134         }
2135
2136         @Override
2137         public void callSessionUssdMessageReceived(ImsCallSession session,
2138                 int mode, String ussdMessage) {
2139             if (DBG) {
2140                 log("callSessionUssdMessageReceived :: session=" + session
2141                         + ", mode=" + mode + ", ussdMessage=" + ussdMessage);
2142             }
2143
2144             ImsCall.Listener listener;
2145
2146             synchronized(ImsCall.this) {
2147                 listener = mListener;
2148             }
2149
2150             if (listener != null) {
2151                 try {
2152                     listener.onCallUssdMessageReceived(ImsCall.this, mode, ussdMessage);
2153                 } catch (Throwable t) {
2154                     loge("callSessionUssdMessageReceived :: ", t);
2155                 }
2156             }
2157         }
2158     }
2159 }