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