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