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