9f58f0a8f15abe20c77d5382573bde0983a4beaa
[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.Iterator;
22 import java.util.Map.Entry;
23 import java.util.Set;
24
25 import android.content.Context;
26 import android.net.Uri;
27 import android.os.Bundle;
28 import android.os.Message;
29 import android.telecom.ConferenceParticipant;
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 import com.android.internal.annotations.VisibleForTesting;
38
39 /**
40  * Handles an IMS voice / video call over LTE. You can instantiate this class with
41  * {@link ImsManager}.
42  *
43  * @hide
44  */
45 public class ImsCall implements ICall {
46     public static final int CALL_STATE_ACTIVE_TO_HOLD = 1;
47     public static final int CALL_STATE_HOLD_TO_ACTIVE = 2;
48
49     // Mode of USSD message
50     public static final int USSD_MODE_NOTIFY = 0;
51     public static final int USSD_MODE_REQUEST = 1;
52
53     private static final String TAG = "ImsCall";
54     private static final boolean DBG = true;
55
56     /**
57      * Listener for events relating to an IMS call, such as when a call is being
58      * recieved ("on ringing") or a call is outgoing ("on calling").
59      * <p>Many of these events are also received by {@link ImsCallSession.Listener}.</p>
60      */
61     public static class Listener {
62         /**
63          * Called when a request is sent out to initiate a new call
64          * and 1xx response is received from the network.
65          * The default implementation calls {@link #onCallStateChanged}.
66          *
67          * @param call the call object that carries out the IMS call
68          */
69         public void onCallProgressing(ImsCall call) {
70             onCallStateChanged(call);
71         }
72
73         /**
74          * Called when the call is established.
75          * The default implementation calls {@link #onCallStateChanged}.
76          *
77          * @param call the call object that carries out the IMS call
78          */
79         public void onCallStarted(ImsCall call) {
80             onCallStateChanged(call);
81         }
82
83         /**
84          * Called when the call setup is failed.
85          * The default implementation calls {@link #onCallError}.
86          *
87          * @param call the call object that carries out the IMS call
88          * @param reasonInfo detailed reason of the call setup failure
89          */
90         public void onCallStartFailed(ImsCall call, ImsReasonInfo reasonInfo) {
91             onCallError(call, reasonInfo);
92         }
93
94         /**
95          * Called when the call is terminated.
96          * The default implementation calls {@link #onCallStateChanged}.
97          *
98          * @param call the call object that carries out the IMS call
99          * @param reasonInfo detailed reason of the call termination
100          */
101         public void onCallTerminated(ImsCall call, ImsReasonInfo reasonInfo) {
102             // Store the call termination reason
103
104             onCallStateChanged(call);
105         }
106
107         /**
108          * Called when the call is in hold.
109          * The default implementation calls {@link #onCallStateChanged}.
110          *
111          * @param call the call object that carries out the IMS call
112          */
113         public void onCallHeld(ImsCall call) {
114             onCallStateChanged(call);
115         }
116
117         /**
118          * Called when the call hold is failed.
119          * The default implementation calls {@link #onCallError}.
120          *
121          * @param call the call object that carries out the IMS call
122          * @param reasonInfo detailed reason of the call hold failure
123          */
124         public void onCallHoldFailed(ImsCall call, ImsReasonInfo reasonInfo) {
125             onCallError(call, reasonInfo);
126         }
127
128         /**
129          * Called when the call hold is received from the remote user.
130          * The default implementation calls {@link #onCallStateChanged}.
131          *
132          * @param call the call object that carries out the IMS call
133          */
134         public void onCallHoldReceived(ImsCall call) {
135             onCallStateChanged(call);
136         }
137
138         /**
139          * Called when the call is in call.
140          * The default implementation calls {@link #onCallStateChanged}.
141          *
142          * @param call the call object that carries out the IMS call
143          */
144         public void onCallResumed(ImsCall call) {
145             onCallStateChanged(call);
146         }
147
148         /**
149          * Called when the call resume is failed.
150          * The default implementation calls {@link #onCallError}.
151          *
152          * @param call the call object that carries out the IMS call
153          * @param reasonInfo detailed reason of the call resume failure
154          */
155         public void onCallResumeFailed(ImsCall call, ImsReasonInfo reasonInfo) {
156             onCallError(call, reasonInfo);
157         }
158
159         /**
160          * Called when the call resume is received from the remote user.
161          * The default implementation calls {@link #onCallStateChanged}.
162          *
163          * @param call the call object that carries out the IMS call
164          */
165         public void onCallResumeReceived(ImsCall call) {
166             onCallStateChanged(call);
167         }
168
169         /**
170          * Called when the call is in call.
171          * The default implementation calls {@link #onCallStateChanged}.
172          *
173          * @param call the call object that carries out the IMS call
174          * @param newCall the call object that is merged with an active & hold call
175          */
176         public void onCallMerged(ImsCall call) {
177             onCallStateChanged(call);
178         }
179
180         /**
181          * Called when the call merge is failed.
182          * The default implementation calls {@link #onCallError}.
183          *
184          * @param call the call object that carries out the IMS call
185          * @param reasonInfo detailed reason of the call merge failure
186          */
187         public void onCallMergeFailed(ImsCall call, ImsReasonInfo reasonInfo) {
188             onCallError(call, reasonInfo);
189         }
190
191         /**
192          * Called when the call is updated (except for hold/unhold).
193          * The default implementation calls {@link #onCallStateChanged}.
194          *
195          * @param call the call object that carries out the IMS call
196          */
197         public void onCallUpdated(ImsCall call) {
198             onCallStateChanged(call);
199         }
200
201         /**
202          * Called when the call update is failed.
203          * The default implementation calls {@link #onCallError}.
204          *
205          * @param call the call object that carries out the IMS call
206          * @param reasonInfo detailed reason of the call update failure
207          */
208         public void onCallUpdateFailed(ImsCall call, ImsReasonInfo reasonInfo) {
209             onCallError(call, reasonInfo);
210         }
211
212         /**
213          * Called when the call update is received from the remote user.
214          *
215          * @param call the call object that carries out the IMS call
216          */
217         public void onCallUpdateReceived(ImsCall call) {
218             // no-op
219         }
220
221         /**
222          * Called when the call is extended to the conference call.
223          * The default implementation calls {@link #onCallStateChanged}.
224          *
225          * @param call the call object that carries out the IMS call
226          * @param newCall the call object that is extended to the conference from the active call
227          */
228         public void onCallConferenceExtended(ImsCall call, ImsCall newCall) {
229             onCallStateChanged(call);
230         }
231
232         /**
233          * Called when the conference extension is failed.
234          * The default implementation calls {@link #onCallError}.
235          *
236          * @param call the call object that carries out the IMS call
237          * @param reasonInfo detailed reason of the conference extension failure
238          */
239         public void onCallConferenceExtendFailed(ImsCall call,
240                 ImsReasonInfo reasonInfo) {
241             onCallError(call, reasonInfo);
242         }
243
244         /**
245          * Called when the conference extension is received from the remote user.
246          *
247          * @param call the call object that carries out the IMS call
248          * @param newCall the call object that is extended to the conference from the active call
249          */
250         public void onCallConferenceExtendReceived(ImsCall call, ImsCall newCall) {
251             onCallStateChanged(call);
252         }
253
254         /**
255          * Called when the invitation request of the participants is delivered to
256          * the conference server.
257          *
258          * @param call the call object that carries out the IMS call
259          */
260         public void onCallInviteParticipantsRequestDelivered(ImsCall call) {
261             // no-op
262         }
263
264         /**
265          * Called when the invitation request of the participants is failed.
266          *
267          * @param call the call object that carries out the IMS call
268          * @param reasonInfo detailed reason of the conference invitation failure
269          */
270         public void onCallInviteParticipantsRequestFailed(ImsCall call,
271                 ImsReasonInfo reasonInfo) {
272             // no-op
273         }
274
275         /**
276          * Called when the removal request of the participants is delivered to
277          * the conference server.
278          *
279          * @param call the call object that carries out the IMS call
280          */
281         public void onCallRemoveParticipantsRequestDelivered(ImsCall call) {
282             // no-op
283         }
284
285         /**
286          * Called when the removal request of the participants is failed.
287          *
288          * @param call the call object that carries out the IMS call
289          * @param reasonInfo detailed reason of the conference removal failure
290          */
291         public void onCallRemoveParticipantsRequestFailed(ImsCall call,
292                 ImsReasonInfo reasonInfo) {
293             // no-op
294         }
295
296         /**
297          * Called when the conference state is updated.
298          *
299          * @param call the call object that carries out the IMS call
300          * @param state state of the participant who is participated in the conference call
301          */
302         public void onCallConferenceStateUpdated(ImsCall call, ImsConferenceState state) {
303             // no-op
304         }
305
306         /**
307          * Called when the state of an IMS conference participant has changed.
308          *
309          * @param call the call object that carries out the IMS call.
310          * @param participant the participant and its new state information.
311          */
312         public void onConferenceParticipantStateChanged(ImsCall call,
313                 ConferenceParticipant participant) {
314             // no-op
315         }
316
317         /**
318          * Called when the USSD message is received from the network.
319          *
320          * @param mode mode of the USSD message (REQUEST / NOTIFY)
321          * @param ussdMessage USSD message
322          */
323         public void onCallUssdMessageReceived(ImsCall call,
324                 int mode, String ussdMessage) {
325             // no-op
326         }
327
328         /**
329          * Called when an error occurs. The default implementation is no op.
330          * overridden. The default implementation is no op. Error events are
331          * not re-directed to this callback and are handled in {@link #onCallError}.
332          *
333          * @param call the call object that carries out the IMS call
334          * @param reasonInfo detailed reason of this error
335          * @see ImsReasonInfo
336          */
337         public void onCallError(ImsCall call, ImsReasonInfo reasonInfo) {
338             // no-op
339         }
340
341         /**
342          * Called when an event occurs and the corresponding callback is not
343          * overridden. The default implementation is no op. Error events are
344          * not re-directed to this callback and are handled in {@link #onCallError}.
345          *
346          * @param call the call object that carries out the IMS call
347          */
348         public void onCallStateChanged(ImsCall call) {
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     private ImsCallSession mTransientConferenceSession = null;
414
415     /**
416      * Create an IMS call object.
417      *
418      * @param context the context for accessing system services
419      * @param profile the call profile to make/take a call
420      */
421     public ImsCall(Context context, ImsCallProfile profile) {
422         mContext = context;
423         mCallProfile = profile;
424     }
425
426     /**
427      * Closes this object. This object is not usable after being closed.
428      */
429     @Override
430     public void close() {
431         synchronized(mLockObj) {
432             destroyCallGroup();
433
434             if (mSession != null) {
435                 mSession.close();
436                 mSession = null;
437             }
438
439             mCallProfile = null;
440             mProposedCallProfile = null;
441             mLastReasonInfo = null;
442             mMediaSession = null;
443         }
444     }
445
446     /**
447      * Checks if the call has a same remote user identity or not.
448      *
449      * @param userId the remote user identity
450      * @return true if the remote user identity is equal; otherwise, false
451      */
452     @Override
453     public boolean checkIfRemoteUserIsSame(String userId) {
454         if (userId == null) {
455             return false;
456         }
457
458         return userId.equals(mCallProfile.getCallExtra(ImsCallProfile.EXTRA_REMOTE_URI, ""));
459     }
460
461     /**
462      * Checks if the call is equal or not.
463      *
464      * @param call the call to be compared
465      * @return true if the call is equal; otherwise, false
466      */
467     @Override
468     public boolean equalsTo(ICall call) {
469         if (call == null) {
470             return false;
471         }
472
473         if (call instanceof ImsCall) {
474             return this.equals(call);
475         }
476
477         return false;
478     }
479
480     /**
481      * Gets the negotiated (local & remote) call profile.
482      *
483      * @return a {@link ImsCallProfile} object that has the negotiated call profile
484      */
485     public ImsCallProfile getCallProfile() {
486         synchronized(mLockObj) {
487             return mCallProfile;
488         }
489     }
490
491     /**
492      * Gets the local call profile (local capabilities).
493      *
494      * @return a {@link ImsCallProfile} object that has the local call profile
495      */
496     public ImsCallProfile getLocalCallProfile() throws ImsException {
497         synchronized(mLockObj) {
498             if (mSession == null) {
499                 throw new ImsException("No call session",
500                         ImsReasonInfo.CODE_LOCAL_CALL_TERMINATED);
501             }
502
503             try {
504                 return mSession.getLocalCallProfile();
505             } catch (Throwable t) {
506                 loge("getLocalCallProfile :: ", t);
507                 throw new ImsException("getLocalCallProfile()", t, 0);
508             }
509         }
510     }
511
512     /**
513      * Gets the call profile proposed by the local/remote user.
514      *
515      * @return a {@link ImsCallProfile} object that has the proposed call profile
516      */
517     public ImsCallProfile getProposedCallProfile() {
518         synchronized(mLockObj) {
519             if (!isInCall()) {
520                 return null;
521             }
522
523             return mProposedCallProfile;
524         }
525     }
526
527     /**
528      * Gets the state of the {@link ImsCallSession} that carries this call.
529      * The value returned must be one of the states in {@link ImsCallSession#State}.
530      *
531      * @return the session state
532      */
533     public int getState() {
534         synchronized(mLockObj) {
535             if (mSession == null) {
536                 return ImsCallSession.State.IDLE;
537             }
538
539             return mSession.getState();
540         }
541     }
542
543     /**
544      * Gets the {@link ImsCallSession} that carries this call.
545      *
546      * @return the session object that carries this call
547      * @hide
548      */
549     public ImsCallSession getCallSession() {
550         synchronized(mLockObj) {
551             return mSession;
552         }
553     }
554
555     /**
556      * Gets the {@link ImsStreamMediaSession} that handles the media operation of this call.
557      * Almost interface APIs are for the VT (Video Telephony).
558      *
559      * @return the media session object that handles the media operation of this call
560      * @hide
561      */
562     public ImsStreamMediaSession getMediaSession() {
563         synchronized(mLockObj) {
564             return mMediaSession;
565         }
566     }
567
568     /**
569      * Gets the specified property of this call.
570      *
571      * @param name key to get the extra call information defined in {@link ImsCallProfile}
572      * @return the extra call information as string
573      */
574     public String getCallExtra(String name) throws ImsException {
575         // Lookup the cache
576
577         synchronized(mLockObj) {
578             // If not found, try to get the property from the remote
579             if (mSession == null) {
580                 throw new ImsException("No call session",
581                         ImsReasonInfo.CODE_LOCAL_CALL_TERMINATED);
582             }
583
584             try {
585                 return mSession.getProperty(name);
586             } catch (Throwable t) {
587                 loge("getCallExtra :: ", t);
588                 throw new ImsException("getCallExtra()", t, 0);
589             }
590         }
591     }
592
593     /**
594      * Gets the last reason information when the call is not established, cancelled or terminated.
595      *
596      * @return the last reason information
597      */
598     public ImsReasonInfo getLastReasonInfo() {
599         synchronized(mLockObj) {
600             return mLastReasonInfo;
601         }
602     }
603
604     /**
605      * Checks if the call has a pending update operation.
606      *
607      * @return true if the call has a pending update operation
608      */
609     public boolean hasPendingUpdate() {
610         synchronized(mLockObj) {
611             return (mUpdateRequest != UPDATE_NONE);
612         }
613     }
614
615     /**
616      * Checks if the call is established.
617      *
618      * @return true if the call is established
619      */
620     public boolean isInCall() {
621         synchronized(mLockObj) {
622             return mInCall;
623         }
624     }
625
626     /**
627      * Checks if the call is muted.
628      *
629      * @return true if the call is muted
630      */
631     public boolean isMuted() {
632         synchronized(mLockObj) {
633             return mMute;
634         }
635     }
636
637     /**
638      * Checks if the call is on hold.
639      *
640      * @return true if the call is on hold
641      */
642     public boolean isOnHold() {
643         synchronized(mLockObj) {
644             return mHold;
645         }
646     }
647
648     /**
649      * Determines if the call is a multiparty call.
650      *
651      * @return {@code True} if the call is a multiparty call.
652      */
653     public boolean isMultiparty() {
654         synchronized(mLockObj) {
655             if (mSession == null) {
656                 return false;
657             }
658
659             return mSession.isMultiparty();
660         }
661     }
662
663     /**
664      * Sets the listener to listen to the IMS call events.
665      * The method calls {@link #setListener setListener(listener, false)}.
666      *
667      * @param listener to listen to the IMS call events of this object; null to remove listener
668      * @see #setListener(Listener, boolean)
669      */
670     public void setListener(ImsCall.Listener listener) {
671         setListener(listener, false);
672     }
673
674     /**
675      * Sets the listener to listen to the IMS call events.
676      * A {@link ImsCall} can only hold one listener at a time. Subsequent calls
677      * to this method override the previous listener.
678      *
679      * @param listener to listen to the IMS call events of this object; null to remove listener
680      * @param callbackImmediately set to true if the caller wants to be called
681      *        back immediately on the current state
682      */
683     public void setListener(ImsCall.Listener listener, boolean callbackImmediately) {
684         boolean inCall;
685         boolean onHold;
686         int state;
687         ImsReasonInfo lastReasonInfo;
688
689         synchronized(mLockObj) {
690             mListener = listener;
691
692             if ((listener == null) || !callbackImmediately) {
693                 return;
694             }
695
696             inCall = mInCall;
697             onHold = mHold;
698             state = getState();
699             lastReasonInfo = mLastReasonInfo;
700         }
701
702         try {
703             if (lastReasonInfo != null) {
704                 listener.onCallError(this, lastReasonInfo);
705             } else if (inCall) {
706                 if (onHold) {
707                     listener.onCallHeld(this);
708                 } else {
709                     listener.onCallStarted(this);
710                 }
711             } else {
712                 switch (state) {
713                     case ImsCallSession.State.ESTABLISHING:
714                         listener.onCallProgressing(this);
715                         break;
716                     case ImsCallSession.State.TERMINATED:
717                         listener.onCallTerminated(this, lastReasonInfo);
718                         break;
719                     default:
720                         // Ignore it. There is no action in the other state.
721                         break;
722                 }
723             }
724         } catch (Throwable t) {
725             loge("setListener()", t);
726         }
727     }
728
729     /**
730      * Mutes or unmutes the mic for the active call.
731      *
732      * @param muted true if the call is muted, false otherwise
733      */
734     public void setMute(boolean muted) throws ImsException {
735         synchronized(mLockObj) {
736             if (mMute != muted) {
737                 mMute = muted;
738
739                 try {
740                     mSession.setMute(muted);
741                 } catch (Throwable t) {
742                     loge("setMute :: ", t);
743                     throwImsException(t, 0);
744                 }
745             }
746         }
747     }
748
749      /**
750       * Attaches an incoming call to this call object.
751       *
752       * @param session the session that receives the incoming call
753       * @throws ImsException if the IMS service fails to attach this object to the session
754       */
755      public void attachSession(ImsCallSession session) throws ImsException {
756          if (DBG) {
757              log("attachSession :: session=" + session);
758          }
759
760          synchronized(mLockObj) {
761              mSession = session;
762
763              try {
764                  mSession.setListener(createCallSessionListener());
765              } catch (Throwable t) {
766                  loge("attachSession :: ", t);
767                  throwImsException(t, 0);
768              }
769          }
770      }
771
772     /**
773      * Initiates an IMS call with the call profile which is provided
774      * when creating a {@link ImsCall}.
775      *
776      * @param session the {@link ImsCallSession} for carrying out the call
777      * @param callee callee information to initiate an IMS call
778      * @throws ImsException if the IMS service fails to initiate the call
779      */
780     public void start(ImsCallSession session, String callee)
781             throws ImsException {
782         if (DBG) {
783             log("start(1) :: session=" + session + ", callee=" + callee);
784         }
785
786         synchronized(mLockObj) {
787             mSession = session;
788
789             try {
790                 session.setListener(createCallSessionListener());
791                 session.start(callee, mCallProfile);
792             } catch (Throwable t) {
793                 loge("start(1) :: ", t);
794                 throw new ImsException("start(1)", t, 0);
795             }
796         }
797     }
798
799     /**
800      * Initiates an IMS conferenca call with the call profile which is provided
801      * when creating a {@link ImsCall}.
802      *
803      * @param session the {@link ImsCallSession} for carrying out the call
804      * @param participants participant list to initiate an IMS conference call
805      * @throws ImsException if the IMS service fails to initiate the call
806      */
807     public void start(ImsCallSession session, String[] participants)
808             throws ImsException {
809         if (DBG) {
810             log("start(n) :: session=" + session + ", callee=" + participants);
811         }
812
813         synchronized(mLockObj) {
814             mSession = session;
815
816             try {
817                 session.setListener(createCallSessionListener());
818                 session.start(participants, mCallProfile);
819             } catch (Throwable t) {
820                 loge("start(n) :: ", t);
821                 throw new ImsException("start(n)", t, 0);
822             }
823         }
824     }
825
826     /**
827      * Accepts a call.
828      *
829      * @see Listener#onCallStarted
830      *
831      * @param callType The call type the user agreed to for accepting the call.
832      * @throws ImsException if the IMS service fails to accept the call
833      */
834     public void accept(int callType) throws ImsException {
835         if (DBG) {
836             log("accept :: session=" + mSession);
837         }
838
839         accept(callType, new ImsStreamMediaProfile());
840     }
841
842     /**
843      * Accepts a call.
844      *
845      * @param callType call type to be answered in {@link ImsCallProfile}
846      * @param profile a media profile to be answered (audio/audio & video, direction, ...)
847      * @see Listener#onCallStarted
848      * @throws ImsException if the IMS service fails to accept the call
849      */
850     public void accept(int callType, ImsStreamMediaProfile profile) throws ImsException {
851         if (DBG) {
852             log("accept :: session=" + mSession
853                     + ", callType=" + callType + ", profile=" + profile);
854         }
855
856         synchronized(mLockObj) {
857             if (mSession == null) {
858                 throw new ImsException("No call to answer",
859                         ImsReasonInfo.CODE_LOCAL_CALL_TERMINATED);
860             }
861
862             try {
863                 mSession.accept(callType, profile);
864             } catch (Throwable t) {
865                 loge("accept :: ", t);
866                 throw new ImsException("accept()", t, 0);
867             }
868
869             if (mInCall && (mProposedCallProfile != null)) {
870                 if (DBG) {
871                     log("accept :: call profile will be updated");
872                 }
873
874                 mCallProfile = mProposedCallProfile;
875                 mProposedCallProfile = null;
876             }
877
878             // Other call update received
879             if (mInCall && (mUpdateRequest == UPDATE_UNSPECIFIED)) {
880                 mUpdateRequest = UPDATE_NONE;
881             }
882         }
883     }
884
885     /**
886      * Rejects a call.
887      *
888      * @param reason reason code to reject an incoming call
889      * @see Listener#onCallStartFailed
890      * @throws ImsException if the IMS service fails to accept the call
891      */
892     public void reject(int reason) throws ImsException {
893         if (DBG) {
894             log("reject :: session=" + mSession + ", reason=" + reason);
895         }
896
897         synchronized(mLockObj) {
898             if (mSession != null) {
899                 mSession.reject(reason);
900             }
901
902             if (mInCall && (mProposedCallProfile != null)) {
903                 if (DBG) {
904                     log("reject :: call profile is not updated; destroy it...");
905                 }
906
907                 mProposedCallProfile = null;
908             }
909
910             // Other call update received
911             if (mInCall && (mUpdateRequest == UPDATE_UNSPECIFIED)) {
912                 mUpdateRequest = UPDATE_NONE;
913             }
914         }
915     }
916
917     /**
918      * Terminates an IMS call.
919      *
920      * @param reason reason code to terminate a call
921      * @throws ImsException if the IMS service fails to terminate the call
922      */
923     public void terminate(int reason) throws ImsException {
924         if (DBG) {
925             log("terminate :: session=" + mSession + ", reason=" + reason);
926         }
927
928         synchronized(mLockObj) {
929             mHold = false;
930             mInCall = false;
931             CallGroup callGroup = getCallGroup();
932
933             if (mSession != null) {
934                 if (callGroup != null && !callGroup.isOwner(ImsCall.this)) {
935                     log("terminate owner of the call group");
936                     ImsCall owner = (ImsCall) callGroup.getOwner();
937                     if (owner != null) {
938                         owner.terminate(reason);
939                         return;
940                     }
941                 }
942                 mSession.terminate(reason);
943             }
944         }
945     }
946
947
948     /**
949      * Puts a call on hold. When succeeds, {@link Listener#onCallHeld} is called.
950      *
951      * @see Listener#onCallHeld, Listener#onCallHoldFailed
952      * @throws ImsException if the IMS service fails to hold the call
953      */
954     public void hold() throws ImsException {
955         if (DBG) {
956             log("hold :: session=" + mSession);
957         }
958
959         // perform operation on owner before doing any local checks: local
960         // call may not have its status updated
961         synchronized (mLockObj) {
962             CallGroup callGroup = mCallGroup;
963             if (callGroup != null && !callGroup.isOwner(ImsCall.this)) {
964                 log("hold owner of the call group");
965                 ImsCall owner = (ImsCall) callGroup.getOwner();
966                 if (owner != null) {
967                     owner.hold();
968                     return;
969                 }
970             }
971         }
972
973         if (isOnHold()) {
974             if (DBG) {
975                 log("hold :: call is already on hold");
976             }
977             return;
978         }
979
980         synchronized(mLockObj) {
981             if (mUpdateRequest != UPDATE_NONE) {
982                 loge("hold :: update is in progress; request=" + mUpdateRequest);
983                 throw new ImsException("Call update is in progress",
984                         ImsReasonInfo.CODE_LOCAL_ILLEGAL_STATE);
985             }
986
987             if (mSession == null) {
988                 loge("hold :: ");
989                 throw new ImsException("No call session",
990                         ImsReasonInfo.CODE_LOCAL_CALL_TERMINATED);
991             }
992
993             mSession.hold(createHoldMediaProfile());
994             // FIXME: update the state on the callback?
995             mHold = true;
996             mUpdateRequest = UPDATE_HOLD;
997         }
998     }
999
1000     /**
1001      * Continues a call that's on hold. When succeeds, {@link Listener#onCallResumed} is called.
1002      *
1003      * @see Listener#onCallResumed, Listener#onCallResumeFailed
1004      * @throws ImsException if the IMS service fails to resume the call
1005      */
1006     public void resume() throws ImsException {
1007         if (DBG) {
1008             log("resume :: session=" + mSession);
1009         }
1010
1011         // perform operation on owner before doing any local checks: local
1012         // call may not have its status updated
1013         synchronized (mLockObj) {
1014             CallGroup callGroup = mCallGroup;
1015             if (callGroup != null && !callGroup.isOwner(ImsCall.this)) {
1016                 log("resume owner of the call group");
1017                 ImsCall owner = (ImsCall) callGroup.getOwner();
1018                 if (owner != null) {
1019                     owner.resume();
1020                     return;
1021                 }
1022             }
1023         }
1024
1025         if (!isOnHold()) {
1026             if (DBG) {
1027                 log("resume :: call is in conversation");
1028             }
1029             return;
1030         }
1031
1032         synchronized(mLockObj) {
1033             if (mUpdateRequest != UPDATE_NONE) {
1034                 loge("resume :: update is in progress; request=" + mUpdateRequest);
1035                 throw new ImsException("Call update is in progress",
1036                         ImsReasonInfo.CODE_LOCAL_ILLEGAL_STATE);
1037             }
1038
1039             if (mSession == null) {
1040                 loge("resume :: ");
1041                 throw new ImsException("No call session",
1042                         ImsReasonInfo.CODE_LOCAL_CALL_TERMINATED);
1043             }
1044
1045             mSession.resume(createResumeMediaProfile());
1046             // FIXME: update the state on the callback?
1047             mHold = false;
1048             mUpdateRequest = UPDATE_RESUME;
1049         }
1050     }
1051
1052     /**
1053      * Merges the active & hold call.
1054      *
1055      * @see Listener#onCallMerged, Listener#onCallMergeFailed
1056      * @throws ImsException if the IMS service fails to merge the call
1057      */
1058     public void merge() throws ImsException {
1059         if (DBG) {
1060             log("merge :: session=" + mSession);
1061         }
1062
1063         synchronized(mLockObj) {
1064             if (mUpdateRequest != UPDATE_NONE) {
1065                 loge("merge :: update is in progress; request=" + mUpdateRequest);
1066                 throw new ImsException("Call update is in progress",
1067                         ImsReasonInfo.CODE_LOCAL_ILLEGAL_STATE);
1068             }
1069
1070             if (mSession == null) {
1071                 loge("merge :: ");
1072                 throw new ImsException("No call session",
1073                         ImsReasonInfo.CODE_LOCAL_CALL_TERMINATED);
1074             }
1075
1076             // if skipHoldBeforeMerge = true, IMS service implementation will
1077             // merge without explicitly holding the call.
1078             if (mHold || (mContext.getResources().getBoolean(
1079                     com.android.internal.R.bool.skipHoldBeforeMerge))) {
1080                 mSession.merge();
1081                 mUpdateRequest = UPDATE_MERGE;
1082             } else {
1083                 // This code basically says, we need to explicitly hold before requesting a merge
1084                 // when we get the callback that the hold was successful (or failed), we should
1085                 // automatically request a merge.
1086                 mSession.hold(createHoldMediaProfile());
1087                 mHold = true;
1088                 mUpdateRequest = UPDATE_HOLD_MERGE;
1089             }
1090         }
1091     }
1092
1093     /**
1094      * Merges the active & hold call.
1095      *
1096      * @param bgCall the background (holding) call
1097      * @see Listener#onCallMerged, Listener#onCallMergeFailed
1098      * @throws ImsException if the IMS service fails to merge the call
1099      */
1100     public void merge(ImsCall bgCall) throws ImsException {
1101         if (DBG) {
1102             log("merge(1) :: session=" + mSession);
1103         }
1104
1105         if (bgCall == null) {
1106             throw new ImsException("No background call",
1107                     ImsReasonInfo.CODE_LOCAL_ILLEGAL_ARGUMENT);
1108         }
1109
1110         synchronized(mLockObj) {
1111             createCallGroup(bgCall);
1112         }
1113
1114         merge();
1115     }
1116
1117     /**
1118      * Updates the current call's properties (ex. call mode change: video upgrade / downgrade).
1119      */
1120     public void update(int callType, ImsStreamMediaProfile mediaProfile) throws ImsException {
1121         if (DBG) {
1122             log("update :: session=" + mSession);
1123         }
1124
1125         if (isOnHold()) {
1126             if (DBG) {
1127                 log("update :: call is on hold");
1128             }
1129             throw new ImsException("Not in a call to update call",
1130                     ImsReasonInfo.CODE_LOCAL_ILLEGAL_STATE);
1131         }
1132
1133         synchronized(mLockObj) {
1134             if (mUpdateRequest != UPDATE_NONE) {
1135                 if (DBG) {
1136                     log("update :: update is in progress; request=" + mUpdateRequest);
1137                 }
1138                 throw new ImsException("Call update is in progress",
1139                         ImsReasonInfo.CODE_LOCAL_ILLEGAL_STATE);
1140             }
1141
1142             if (mSession == null) {
1143                 loge("update :: ");
1144                 throw new ImsException("No call session",
1145                         ImsReasonInfo.CODE_LOCAL_CALL_TERMINATED);
1146             }
1147
1148             mSession.update(callType, mediaProfile);
1149             mUpdateRequest = UPDATE_UNSPECIFIED;
1150         }
1151     }
1152
1153     /**
1154      * Extends this call (1-to-1 call) to the conference call
1155      * inviting the specified participants to.
1156      *
1157      */
1158     public void extendToConference(String[] participants) throws ImsException {
1159         if (DBG) {
1160             log("extendToConference :: session=" + mSession);
1161         }
1162
1163         if (isOnHold()) {
1164             if (DBG) {
1165                 log("extendToConference :: call is on hold");
1166             }
1167             throw new ImsException("Not in a call to extend a call to conference",
1168                     ImsReasonInfo.CODE_LOCAL_ILLEGAL_STATE);
1169         }
1170
1171         synchronized(mLockObj) {
1172             if (mUpdateRequest != UPDATE_NONE) {
1173                 if (DBG) {
1174                     log("extendToConference :: update is in progress; request=" + mUpdateRequest);
1175                 }
1176                 throw new ImsException("Call update is in progress",
1177                         ImsReasonInfo.CODE_LOCAL_ILLEGAL_STATE);
1178             }
1179
1180             if (mSession == null) {
1181                 loge("extendToConference :: ");
1182                 throw new ImsException("No call session",
1183                         ImsReasonInfo.CODE_LOCAL_CALL_TERMINATED);
1184             }
1185
1186             mSession.extendToConference(participants);
1187             mUpdateRequest = UPDATE_EXTEND_TO_CONFERENCE;
1188         }
1189     }
1190
1191     /**
1192      * Requests the conference server to invite an additional participants to the conference.
1193      *
1194      */
1195     public void inviteParticipants(String[] participants) throws ImsException {
1196         if (DBG) {
1197             log("inviteParticipants :: session=" + mSession);
1198         }
1199
1200         synchronized(mLockObj) {
1201             if (mSession == null) {
1202                 loge("inviteParticipants :: ");
1203                 throw new ImsException("No call session",
1204                         ImsReasonInfo.CODE_LOCAL_CALL_TERMINATED);
1205             }
1206
1207             mSession.inviteParticipants(participants);
1208         }
1209     }
1210
1211     /**
1212      * Requests the conference server to remove the specified participants from the conference.
1213      *
1214      */
1215     public void removeParticipants(String[] participants) throws ImsException {
1216         if (DBG) {
1217             log("removeParticipants :: session=" + mSession);
1218         }
1219
1220         synchronized(mLockObj) {
1221             if (mSession == null) {
1222                 loge("removeParticipants :: ");
1223                 throw new ImsException("No call session",
1224                         ImsReasonInfo.CODE_LOCAL_CALL_TERMINATED);
1225             }
1226
1227             mSession.removeParticipants(participants);
1228         }
1229     }
1230
1231
1232     /**
1233      * Sends a DTMF code. According to <a href="http://tools.ietf.org/html/rfc2833">RFC 2833</a>,
1234      * event 0 ~ 9 maps to decimal value 0 ~ 9, '*' to 10, '#' to 11, event 'A' ~ 'D' to 12 ~ 15,
1235      * and event flash to 16. Currently, event flash is not supported.
1236      *
1237      * @param char that represents the DTMF digit to send.
1238      */
1239     public void sendDtmf(char c) {
1240         sendDtmf(c, null);
1241     }
1242
1243     /**
1244      * Sends a DTMF code. According to <a href="http://tools.ietf.org/html/rfc2833">RFC 2833</a>,
1245      * event 0 ~ 9 maps to decimal value 0 ~ 9, '*' to 10, '#' to 11, event 'A' ~ 'D' to 12 ~ 15,
1246      * and event flash to 16. Currently, event flash is not supported.
1247      *
1248      * @param c that represents the DTMF to send. '0' ~ '9', 'A' ~ 'D', '*', '#' are valid inputs.
1249      * @param result the result message to send when done.
1250      */
1251     public void sendDtmf(char c, Message result) {
1252         if (DBG) {
1253             log("sendDtmf :: session=" + mSession + ", code=" + c);
1254         }
1255
1256         synchronized(mLockObj) {
1257             if (mSession != null) {
1258                 mSession.sendDtmf(c);
1259             }
1260         }
1261
1262         if (result != null) {
1263             result.sendToTarget();
1264         }
1265     }
1266
1267     /**
1268      * Sends an USSD message.
1269      *
1270      * @param ussdMessage USSD message to send
1271      */
1272     public void sendUssd(String ussdMessage) throws ImsException {
1273         if (DBG) {
1274             log("sendUssd :: session=" + mSession + ", ussdMessage=" + ussdMessage);
1275         }
1276
1277         synchronized(mLockObj) {
1278             if (mSession == null) {
1279                 loge("sendUssd :: ");
1280                 throw new ImsException("No call session",
1281                         ImsReasonInfo.CODE_LOCAL_CALL_TERMINATED);
1282             }
1283
1284             mSession.sendUssd(ussdMessage);
1285         }
1286     }
1287
1288     private void clear(ImsReasonInfo lastReasonInfo) {
1289         mInCall = false;
1290         mHold = false;
1291         mUpdateRequest = UPDATE_NONE;
1292         mLastReasonInfo = lastReasonInfo;
1293         destroyCallGroup();
1294     }
1295
1296     private void createCallGroup(ImsCall neutralReferrer) {
1297         CallGroup referrerCallGroup = neutralReferrer.getCallGroup();
1298
1299         if (mCallGroup == null) {
1300             if (referrerCallGroup == null) {
1301                 mCallGroup = CallGroupManager.getInstance().createCallGroup(new ImsCallGroup());
1302             } else {
1303                 mCallGroup = referrerCallGroup;
1304             }
1305
1306             if (mCallGroup != null) {
1307                 mCallGroup.setNeutralReferrer(neutralReferrer);
1308             }
1309         } else {
1310             mCallGroup.setNeutralReferrer(neutralReferrer);
1311
1312             if ((referrerCallGroup != null)
1313                     && (mCallGroup != referrerCallGroup)) {
1314                 loge("fatal :: call group is mismatched; call is corrupted...");
1315             }
1316         }
1317     }
1318
1319     private void updateCallGroup(ImsCall owner) {
1320         if (mCallGroup == null) {
1321             return;
1322         }
1323
1324         ImsCall neutralReferrer = (ImsCall)mCallGroup.getNeutralReferrer();
1325
1326         if (owner == null) {
1327             // Maintain the call group if the current call has been merged in the past.
1328             if (!mCallGroup.hasReferrer()) {
1329                 CallGroupManager.getInstance().destroyCallGroup(mCallGroup);
1330                 mCallGroup = null;
1331             }
1332         } else {
1333             mCallGroup.addReferrer(this);
1334
1335             if (neutralReferrer != null) {
1336                 if (neutralReferrer.getCallGroup() == null) {
1337                     neutralReferrer.setCallGroup(mCallGroup);
1338                     mCallGroup.addReferrer(neutralReferrer);
1339                 }
1340
1341                 neutralReferrer.enforceConversationMode();
1342             }
1343
1344             // Close the existing owner call if present
1345             ImsCall exOwner = (ImsCall)mCallGroup.getOwner();
1346
1347             mCallGroup.setOwner(owner);
1348
1349             if (exOwner != null) {
1350                 exOwner.close();
1351             }
1352         }
1353     }
1354
1355     private void destroyCallGroup() {
1356         if (mCallGroup == null) {
1357             return;
1358         }
1359
1360         mCallGroup.removeReferrer(this);
1361
1362         if (!mCallGroup.hasReferrer()) {
1363             CallGroupManager.getInstance().destroyCallGroup(mCallGroup);
1364         }
1365
1366         mCallGroup = null;
1367     }
1368
1369     public CallGroup getCallGroup() {
1370         synchronized(mLockObj) {
1371             return mCallGroup;
1372         }
1373     }
1374
1375     private void setCallGroup(CallGroup callGroup) {
1376         synchronized(mLockObj) {
1377             mCallGroup = callGroup;
1378         }
1379     }
1380
1381     /**
1382      * Creates an IMS call session listener.
1383      */
1384     private ImsCallSession.Listener createCallSessionListener() {
1385         return new ImsCallSessionListenerProxy();
1386     }
1387
1388     private ImsCall createNewCall(ImsCallSession session, ImsCallProfile profile) {
1389         ImsCall call = new ImsCall(mContext, profile);
1390
1391         try {
1392             call.attachSession(session);
1393         } catch (ImsException e) {
1394             if (call != null) {
1395                 call.close();
1396                 call = null;
1397             }
1398         }
1399
1400         // Do additional operations...
1401
1402         return call;
1403     }
1404
1405     private ImsStreamMediaProfile createHoldMediaProfile() {
1406         ImsStreamMediaProfile mediaProfile = new ImsStreamMediaProfile();
1407
1408         if (mCallProfile == null) {
1409             return mediaProfile;
1410         }
1411
1412         mediaProfile.mAudioQuality = mCallProfile.mMediaProfile.mAudioQuality;
1413         mediaProfile.mVideoQuality = mCallProfile.mMediaProfile.mVideoQuality;
1414         mediaProfile.mAudioDirection = ImsStreamMediaProfile.DIRECTION_SEND;
1415
1416         if (mediaProfile.mVideoQuality != ImsStreamMediaProfile.VIDEO_QUALITY_NONE) {
1417             mediaProfile.mVideoDirection = ImsStreamMediaProfile.DIRECTION_SEND;
1418         }
1419
1420         return mediaProfile;
1421     }
1422
1423     private ImsStreamMediaProfile createResumeMediaProfile() {
1424         ImsStreamMediaProfile mediaProfile = new ImsStreamMediaProfile();
1425
1426         if (mCallProfile == null) {
1427             return mediaProfile;
1428         }
1429
1430         mediaProfile.mAudioQuality = mCallProfile.mMediaProfile.mAudioQuality;
1431         mediaProfile.mVideoQuality = mCallProfile.mMediaProfile.mVideoQuality;
1432         mediaProfile.mAudioDirection = ImsStreamMediaProfile.DIRECTION_SEND_RECEIVE;
1433
1434         if (mediaProfile.mVideoQuality != ImsStreamMediaProfile.VIDEO_QUALITY_NONE) {
1435             mediaProfile.mVideoDirection = ImsStreamMediaProfile.DIRECTION_SEND_RECEIVE;
1436         }
1437
1438         return mediaProfile;
1439     }
1440
1441     private void enforceConversationMode() {
1442         if (mInCall) {
1443             mHold = false;
1444             mUpdateRequest = UPDATE_NONE;
1445         }
1446     }
1447
1448     private void mergeInternal() {
1449         if (DBG) {
1450             log("mergeInternal :: session=" + mSession);
1451         }
1452
1453         mSession.merge();
1454         mUpdateRequest = UPDATE_MERGE;
1455     }
1456
1457     private void notifyConferenceSessionTerminated(ImsReasonInfo reasonInfo) {
1458         ImsCall.Listener listener;
1459         if (mCallGroup.isOwner(ImsCall.this)) {
1460             log("Group Owner! Size of referrers list = " + mCallGroup.getReferrers().size());
1461             while (mCallGroup.hasReferrer()) {
1462                 ImsCall call = (ImsCall) mCallGroup.getReferrers().get(0);
1463                 log("onCallTerminated to be called for the call:: " + call);
1464
1465                 if (call == null) {
1466                     continue;
1467                 }
1468
1469                 listener = call.mListener;
1470                 call.clear(reasonInfo);
1471
1472                 if (listener != null) {
1473                     try {
1474                         listener.onCallTerminated(call, reasonInfo);
1475                     } catch (Throwable t) {
1476                         loge("notifyConferenceSessionTerminated :: ", t);
1477                     }
1478                 }
1479             }
1480         } else if (!mCallGroup.isReferrer(ImsCall.this)) {
1481             return;
1482         }
1483
1484         listener = mListener;
1485         clear(reasonInfo);
1486
1487         if (listener != null) {
1488             try {
1489                 listener.onCallTerminated(this, reasonInfo);
1490             } catch (Throwable t) {
1491                 loge("notifyConferenceSessionTerminated :: ", t);
1492             }
1493         }
1494     }
1495
1496     private void notifyConferenceStateUpdatedThroughGroupOwner(int update) {
1497         ImsCall.Listener listener;
1498
1499         if (mCallGroup.isOwner(ImsCall.this)) {
1500             log("Group Owner! Size of referrers list = " + mCallGroup.getReferrers().size());
1501             for (ICall icall : mCallGroup.getReferrers()) {
1502                 ImsCall call = (ImsCall) icall;
1503                 log("notifyConferenceStateUpdatedThroughGroupOwner to be called for the call:: " +
1504                         call);
1505
1506                 if (call == null) {
1507                     continue;
1508                 }
1509
1510                 listener = call.mListener;
1511
1512                 if (listener != null) {
1513                     try {
1514                         switch (update) {
1515                             case UPDATE_HOLD:
1516                                 listener.onCallHeld(call);
1517                                 break;
1518                             case UPDATE_RESUME:
1519                                 listener.onCallResumed(call);
1520                                 break;
1521                             default:
1522                                 loge("notifyConferenceStateUpdatedThroughGroupOwner :: not " +
1523                                         "handled update " + update);
1524                         }
1525                     } catch (Throwable t) {
1526                         loge("notifyConferenceStateUpdatedThroughGroupOwner :: ", t);
1527                     }
1528                 }
1529             }
1530         }
1531     }
1532
1533     private void notifyConferenceStateUpdated(ImsConferenceState state) {
1534         Set<Entry<String, Bundle>> paticipants = state.mParticipants.entrySet();
1535
1536         if (paticipants == null) {
1537             return;
1538         }
1539
1540         Iterator<Entry<String, Bundle>> iterator = paticipants.iterator();
1541
1542         while (iterator.hasNext()) {
1543             Entry<String, Bundle> entry = iterator.next();
1544
1545             String key = entry.getKey();
1546             Bundle confInfo = entry.getValue();
1547             String status = confInfo.getString(ImsConferenceState.STATUS);
1548             String user = confInfo.getString(ImsConferenceState.USER);
1549             String displayName = confInfo.getString(ImsConferenceState.DISPLAY_TEXT);
1550             String endpoint = confInfo.getString(ImsConferenceState.ENDPOINT);
1551
1552             if (DBG) {
1553                 log("notifyConferenceStateUpdated :: key=" + key +
1554                         ", status=" + status +
1555                         ", user=" + user +
1556                         ", displayName= " + displayName +
1557                         ", endpoint=" + endpoint);
1558             }
1559
1560             if ((mCallGroup != null) && (!mCallGroup.isOwner(ImsCall.this))) {
1561                 continue;
1562             }
1563
1564             // Attempt to find the participant in the call group if it exists.
1565             ImsCall referrer = null;
1566             if (mCallGroup != null) {
1567                 referrer = (ImsCall) mCallGroup.getReferrer(endpoint);
1568             }
1569
1570             // Participant is not being represented by an ImsCall, so handle as generic participant.
1571             // Notify the {@code ImsPhoneCallTracker} of the participant state change so that it
1572             // can be passed up to the {@code TelephonyConferenceController}.
1573             if (referrer == null) {
1574                 Uri handle = Uri.parse(user);
1575                 Uri endpointUri = Uri.parse(endpoint);
1576                 int connectionState = ImsConferenceState.getConnectionStateForStatus(status);
1577
1578                 ConferenceParticipant conferenceParticipant = new ConferenceParticipant(handle,
1579                         displayName, endpointUri, connectionState);
1580                 if (mListener != null) {
1581                     try {
1582                         mListener.onConferenceParticipantStateChanged(this, conferenceParticipant);
1583                     } catch (Throwable t) {
1584                         loge("notifyConferenceStateUpdated :: ", t);
1585                     }
1586                 }
1587                 continue;
1588             }
1589
1590             if (referrer.mListener == null) {
1591                 continue;
1592             }
1593
1594             try {
1595                 if (status.equals(ImsConferenceState.STATUS_ALERTING)) {
1596                     referrer.mListener.onCallProgressing(referrer);
1597                 }
1598                 else if (status.equals(ImsConferenceState.STATUS_CONNECT_FAIL)) {
1599                     referrer.mListener.onCallStartFailed(referrer, new ImsReasonInfo());
1600                 }
1601                 else if (status.equals(ImsConferenceState.STATUS_ON_HOLD)) {
1602                     referrer.mListener.onCallHoldReceived(referrer);
1603                 }
1604                 else if (status.equals(ImsConferenceState.STATUS_CONNECTED)) {
1605                     referrer.mListener.onCallStarted(referrer);
1606                 }
1607                 else if (status.equals(ImsConferenceState.STATUS_DISCONNECTED)) {
1608                     referrer.clear(new ImsReasonInfo());
1609                     referrer.mListener.onCallTerminated(referrer, referrer.mLastReasonInfo);
1610                 }
1611             } catch (Throwable t) {
1612                 loge("notifyConferenceStateUpdated :: ", t);
1613             }
1614         }
1615     }
1616
1617
1618     /**
1619      * This function determines if the ImsCallSession is our actual ImsCallSession or if is
1620      * the transient session used in the process of creating a conference. This function should only
1621      * be called within  callbacks that are not directly related to conference merging but might
1622      * potentially still be called on the transient ImsCallSession sent to us from
1623      * callSessionMergeStarted() when we don't really care. In those situations, we probably don't
1624      * want to take any action so we need to know that we can return early.
1625      *
1626      * @param session - The {@link ImsCallSession} that the function needs to analyze
1627      * @return true if this is the transient {@link ImsCallSession}, false otherwise.
1628      */
1629     private boolean isTransientConferenceSession(ImsCallSession session) {
1630         if (session != null && session != mSession && session == mTransientConferenceSession) {
1631             return true;
1632         }
1633         return false;
1634     }
1635
1636     /**
1637      * We received a callback from ImsCallSession that a merge was complete. Clean up all
1638      * internal state to represent this state change.
1639      *
1640      * @param session The {@link ImsCallSession} that is the host of this conference.
1641      */
1642     private void processMergeComplete(ImsCallSession session) {
1643         // If mTransientConferenceSession is set, this means that this is a newly created
1644         // conference. Otherwise, we are adding to an existing conference.
1645         ImsCall.Listener listener;
1646         synchronized(ImsCall.this) {
1647             listener = mListener;
1648             // TODO: See if we can use the session param to do some further checks here.
1649             if (mTransientConferenceSession != null) {
1650                 // We need to replace our ImsCallSession with this new one after adjusting
1651                 // the listeners on the session.
1652                 mSession.setListener(null);
1653                 mTransientConferenceSession.setListener(createCallSessionListener());
1654                 mSession = mTransientConferenceSession;
1655             }
1656             mUpdateRequest = UPDATE_NONE;
1657         }
1658         if (listener != null) {
1659             try {
1660                 listener.onCallMerged(ImsCall.this);
1661             } catch (Throwable t) {
1662                 loge("callSessionMergeStarted :: ", t);
1663             }
1664         }
1665
1666         return;
1667     }
1668
1669     /**
1670      * We received a callback from ImsCallSession that a merge failed. Clean up all
1671      * internal state to represent this state change.
1672      *
1673      * @param session The {@link ImsCallSession} that is the host of this conference.
1674      * @param reasonInfo The {@link ImsReasonInfo} why the merge failed.
1675      */
1676     private void processMergeFailed(ImsCallSession session, ImsReasonInfo reasonInfo) {
1677         // If mTransientConferenceSession is set, this means that this is a newly created
1678         // conference. Otherwise, we are adding to an existing conference.
1679         ImsCall.Listener listener;
1680         synchronized(ImsCall.this) {
1681             listener = mListener;
1682             // TODO: See if we can use the session param to do some further checks here.
1683             if (mTransientConferenceSession != null) {
1684                 // Clean up any work that we performed on the transient session.
1685                 mTransientConferenceSession.setListener(null);
1686                 mTransientConferenceSession = null;
1687             }
1688             mUpdateRequest = UPDATE_NONE;
1689         }
1690         if (listener != null) {
1691             try {
1692                 listener.onCallMergeFailed(ImsCall.this, reasonInfo);
1693             } catch (Throwable t) {
1694                 loge("callSessionMergeFailed :: ", t);
1695             }
1696         }
1697         return;
1698     }
1699
1700     private void notifyError(int reason, int statusCode, String message) {
1701     }
1702
1703     private void throwImsException(Throwable t, int code) throws ImsException {
1704         if (t instanceof ImsException) {
1705             throw (ImsException) t;
1706         } else {
1707             throw new ImsException(String.valueOf(code), t, code);
1708         }
1709     }
1710
1711     private void log(String s) {
1712         Rlog.d(TAG, s);
1713     }
1714
1715     private void loge(String s) {
1716         Rlog.e(TAG, s);
1717     }
1718
1719     private void loge(String s, Throwable t) {
1720         Rlog.e(TAG, s, t);
1721     }
1722
1723     private class ImsCallSessionListenerProxy extends ImsCallSession.Listener {
1724         @Override
1725         public void callSessionProgressing(ImsCallSession session, ImsStreamMediaProfile profile) {
1726             if (isTransientConferenceSession(session)) {
1727                 log("callSessionProgressing :: not supported for conference session=" + session);
1728                 return;
1729             }
1730
1731             if (DBG) {
1732                 log("callSessionProgressing :: session=" + session + ", profile=" + profile);
1733             }
1734
1735             ImsCall.Listener listener;
1736
1737             synchronized(ImsCall.this) {
1738                 listener = mListener;
1739                 mCallProfile.mMediaProfile.copyFrom(profile);
1740             }
1741
1742             if (listener != null) {
1743                 try {
1744                     listener.onCallProgressing(ImsCall.this);
1745                 } catch (Throwable t) {
1746                     loge("callSessionProgressing :: ", t);
1747                 }
1748             }
1749         }
1750
1751         @Override
1752         public void callSessionStarted(ImsCallSession session, ImsCallProfile profile) {
1753             if (isTransientConferenceSession(session)) {
1754                 log("callSessionStarted :: not supported for conference session=" + session);
1755                 return;
1756             }
1757
1758             if (DBG) {
1759                 log("callSessionStarted :: session=" + session + ", profile=" + profile);
1760             }
1761
1762             ImsCall.Listener listener;
1763
1764             synchronized(ImsCall.this) {
1765                 listener = mListener;
1766                 mCallProfile = profile;
1767             }
1768
1769             if (listener != null) {
1770                 try {
1771                     listener.onCallStarted(ImsCall.this);
1772                 } catch (Throwable t) {
1773                     loge("callSessionStarted :: ", t);
1774                 }
1775             }
1776         }
1777
1778         @Override
1779         public void callSessionStartFailed(ImsCallSession session, ImsReasonInfo reasonInfo) {
1780             if (isTransientConferenceSession(session)) {
1781                 log("callSessionStartFailed :: not supported for conference session=" + session);
1782                 return;
1783             }
1784
1785             if (DBG) {
1786                 log("callSessionStartFailed :: session=" + session +
1787                         ", reasonInfo=" + reasonInfo);
1788             }
1789
1790             ImsCall.Listener listener;
1791
1792             synchronized(ImsCall.this) {
1793                 listener = mListener;
1794                 mLastReasonInfo = reasonInfo;
1795             }
1796
1797             if (listener != null) {
1798                 try {
1799                     listener.onCallStartFailed(ImsCall.this, reasonInfo);
1800                 } catch (Throwable t) {
1801                     loge("callSessionStarted :: ", t);
1802                 }
1803             }
1804         }
1805
1806         @Override
1807         public void callSessionTerminated(ImsCallSession session, ImsReasonInfo reasonInfo) {
1808             if (isTransientConferenceSession(session)) {
1809                 log("callSessionTerminated :: not supported for conference session=" + session);
1810                 return;
1811             }
1812
1813             if (DBG) {
1814                 log("callSessionTerminated :: session=" + session +
1815                         ", reasonInfo=" + reasonInfo);
1816             }
1817
1818             ImsCall.Listener listener = null;
1819
1820             synchronized(ImsCall.this) {
1821                 // Let's explicitly handle this situation here...
1822                 // The ImsCallSession that has just notified that it was terminated could be in the
1823                 // state where it is waiting for a merge to happen. If this is the case, let's just
1824                 // assume that the merge is going to succeed but the callbacks are being done in the
1825                 // wrong order.  We would expect callSessionMergeComplete() to be called on the
1826                 // transient session first and then this call to the original merge() "host".
1827                 // If the timing is off, we could be getting this call first.
1828                 if (mUpdateRequest == UPDATE_MERGE) {
1829                     if (mTransientConferenceSession != null) {
1830                         log("callSessionTerminated() :: called while waiting for a new conference");
1831                         // Let's assume that the merge will complete and the transient session will,
1832                         // in fact, be our new session soon enough.
1833                         mSession.setListener(null);
1834                         mTransientConferenceSession.setListener(createCallSessionListener());
1835                         mSession = mTransientConferenceSession;
1836                         // At this point, we'll handle this conference call like it was a merge()
1837                         // on an existing conference as opposed to creating a new conference. All
1838                         // properly callbacks should be made.
1839
1840                         // TODO: Do we need to handle the case where the new ImsCallSession still
1841                         // fails to merge after this?  Should be a very unlikely scenario.
1842                         return;
1843                     }
1844                 }
1845
1846                 if (mCallGroup != null) {
1847                     notifyConferenceSessionTerminated(reasonInfo);
1848                 } else {
1849                     listener = mListener;
1850                     clear(reasonInfo);
1851                 }
1852             }
1853
1854             if (listener != null) {
1855                 try {
1856                     listener.onCallTerminated(ImsCall.this, reasonInfo);
1857                 } catch (Throwable t) {
1858                     loge("callSessionTerminated :: ", t);
1859                 }
1860             }
1861         }
1862
1863         @Override
1864         public void callSessionHeld(ImsCallSession session, ImsCallProfile profile) {
1865             if (isTransientConferenceSession(session)) {
1866                 log("callSessionHeld :: not supported for conference session=" + session);
1867                 return;
1868             }
1869
1870             if (DBG) {
1871                 log("callSessionHeld :: session=" + session + ", profile=" + profile);
1872             }
1873
1874             ImsCall.Listener listener;
1875
1876             synchronized(ImsCall.this) {
1877                 mCallProfile = profile;
1878
1879                 if (mUpdateRequest == UPDATE_HOLD_MERGE) {
1880                     mergeInternal();
1881                     return;
1882                 }
1883
1884                 mUpdateRequest = UPDATE_NONE;
1885                 listener = mListener;
1886             }
1887
1888             if (listener != null) {
1889                 try {
1890                     listener.onCallHeld(ImsCall.this);
1891                 } catch (Throwable t) {
1892                     loge("callSessionHeld :: ", t);
1893                 }
1894             }
1895
1896             if (mCallGroup != null) {
1897                 notifyConferenceStateUpdatedThroughGroupOwner(UPDATE_HOLD);
1898             }
1899         }
1900
1901         @Override
1902         public void callSessionHoldFailed(ImsCallSession session, ImsReasonInfo reasonInfo) {
1903             if (isTransientConferenceSession(session)) {
1904                 log("callSessionHoldFailed :: not supported for conference session=" + session);
1905                 return;
1906             }
1907
1908             if (DBG) {
1909                 log("callSessionHoldFailed :: session=" + session +
1910                         ", reasonInfo=" + reasonInfo);
1911             }
1912
1913             boolean isHoldForMerge = false;
1914             ImsCall.Listener listener;
1915
1916             synchronized(ImsCall.this) {
1917                 if (mUpdateRequest == UPDATE_HOLD_MERGE) {
1918                     isHoldForMerge = true;
1919                 }
1920
1921                 mUpdateRequest = UPDATE_NONE;
1922                 listener = mListener;
1923             }
1924
1925             if (isHoldForMerge) {
1926                 // Is hold for merge implemented/supported? If so we need to take a close look
1927                 // at this workflow to make sure that we handle the case where
1928                 // callSessionMergeFailed() does the right thing because we have not actually
1929                 // started the merge yet.
1930                 callSessionMergeFailed(session, reasonInfo);
1931                 return;
1932             }
1933
1934             if (listener != null) {
1935                 try {
1936                     listener.onCallHoldFailed(ImsCall.this, reasonInfo);
1937                 } catch (Throwable t) {
1938                     loge("callSessionHoldFailed :: ", t);
1939                 }
1940             }
1941         }
1942
1943         @Override
1944         public void callSessionHoldReceived(ImsCallSession session, ImsCallProfile profile) {
1945             if (isTransientConferenceSession(session)) {
1946                 log("callSessionHoldReceived :: not supported for conference session=" + session);
1947                 return;
1948             }
1949
1950             if (DBG) {
1951                 log("callSessionHoldReceived :: session=" + session + ", profile=" + profile);
1952             }
1953
1954             ImsCall.Listener listener;
1955
1956             synchronized(ImsCall.this) {
1957                 listener = mListener;
1958                 mCallProfile = profile;
1959             }
1960
1961             if (listener != null) {
1962                 try {
1963                     listener.onCallHoldReceived(ImsCall.this);
1964                 } catch (Throwable t) {
1965                     loge("callSessionHoldReceived :: ", t);
1966                 }
1967             }
1968         }
1969
1970         @Override
1971         public void callSessionResumed(ImsCallSession session, ImsCallProfile profile) {
1972             if (isTransientConferenceSession(session)) {
1973                 log("callSessionResumed :: not supported for conference session=" + session);
1974                 return;
1975             }
1976
1977             if (DBG) {
1978                 log("callSessionResumed :: session=" + session + ", profile=" + profile);
1979             }
1980
1981             ImsCall.Listener listener;
1982
1983             synchronized(ImsCall.this) {
1984                 listener = mListener;
1985                 mCallProfile = profile;
1986                 mUpdateRequest = UPDATE_NONE;
1987             }
1988
1989             if (listener != null) {
1990                 try {
1991                     listener.onCallResumed(ImsCall.this);
1992                 } catch (Throwable t) {
1993                     loge("callSessionResumed :: ", t);
1994                 }
1995             }
1996
1997             if (mCallGroup != null) {
1998                 notifyConferenceStateUpdatedThroughGroupOwner(UPDATE_RESUME);
1999             }
2000         }
2001
2002         @Override
2003         public void callSessionResumeFailed(ImsCallSession session, ImsReasonInfo reasonInfo) {
2004             if (isTransientConferenceSession(session)) {
2005                 log("callSessionResumeFailed :: not supported for conference session=" + session);
2006                 return;
2007             }
2008
2009             if (DBG) {
2010                 log("callSessionResumeFailed :: session=" + session +
2011                         ", reasonInfo=" + reasonInfo);
2012             }
2013
2014             ImsCall.Listener listener;
2015
2016             synchronized(ImsCall.this) {
2017                 listener = mListener;
2018                 mUpdateRequest = UPDATE_NONE;
2019             }
2020
2021             if (listener != null) {
2022                 try {
2023                     listener.onCallResumeFailed(ImsCall.this, reasonInfo);
2024                 } catch (Throwable t) {
2025                     loge("callSessionResumeFailed :: ", t);
2026                 }
2027             }
2028         }
2029
2030         @Override
2031         public void callSessionResumeReceived(ImsCallSession session, ImsCallProfile profile) {
2032             if (isTransientConferenceSession(session)) {
2033                 log("callSessionResumeReceived :: not supported for conference session=" + session);
2034                 return;
2035             }
2036
2037             if (DBG) {
2038                 log("callSessionResumeReceived :: session=" + session +
2039                         ", profile=" + profile);
2040             }
2041
2042             ImsCall.Listener listener;
2043
2044             synchronized(ImsCall.this) {
2045                 listener = mListener;
2046                 mCallProfile = profile;
2047             }
2048
2049             if (listener != null) {
2050                 try {
2051                     listener.onCallResumeReceived(ImsCall.this);
2052                 } catch (Throwable t) {
2053                     loge("callSessionResumeReceived :: ", t);
2054                 }
2055             }
2056         }
2057
2058         @Override
2059         public void callSessionMergeStarted(ImsCallSession session,
2060                 ImsCallSession newSession, ImsCallProfile profile) {
2061             if (DBG) {
2062                 String sessionString = newSession == null ? "null" : newSession.toString();
2063                 String newSessionString = newSession == null ? "null" : newSession.toString();
2064                 log("callSessionMergeStarted :: session=" + sessionString
2065                         + ", newSession=" + newSessionString + ", profile=" + profile);
2066             }
2067
2068             if (mUpdateRequest != UPDATE_MERGE) {
2069                 // Odd, we are not in the midst of merging anything.
2070                 if (DBG) {
2071                     log("callSessionMergeStarted :: no merge in progress.");
2072                 }
2073                 return;
2074             }
2075
2076             // There are 2 ways that we can go here.  If the session that supplied the params
2077             // is not null, then it is the new session that represents the new conference
2078             // if the merge succeeds. If it is null, the merge is happening on our current
2079             // ImsCallSession.
2080             if (session == null) {
2081                 // Everything is already set up and we just need to make sure
2082                 // that we properly respond to all the future callbacks about
2083                 // this merge.
2084                 if (DBG) {
2085                     log("callSessionMergeStarted :: merging into existing ImsCallSession");
2086                 }
2087                 return;
2088             }
2089
2090             if (DBG) {
2091                 log("callSessionMergeStarted ::  setting our transient ImsCallSession");
2092             }
2093
2094             // If we are here, this means that we are creating a new conference and
2095             // we need to do some extra work around managing a new ImsCallSession that
2096             // could represent our new ImsCallSession if the merge succeeds.
2097             synchronized(ImsCall.this) {
2098                 // Keep track of this session for future callbacks to indicate success
2099                 // or failure of this merge.
2100                 mTransientConferenceSession = newSession;
2101                 mTransientConferenceSession.setListener(createCallSessionListener());
2102             }
2103
2104             // STOPSHIP: For now, let's force the complete callback since it is not coming
2105             // from the vendor layer as of when this code was written.
2106             log("callSessionMergeStarted ::  forcing a success callback");
2107             callSessionMergeComplete(mTransientConferenceSession);
2108
2109             return;
2110         }
2111
2112         @Override
2113         public void callSessionMergeComplete(ImsCallSession session) {
2114             if (DBG) {
2115                 String sessionString = session == null ? "null" : session.toString();
2116                 log("callSessionMergeComplete :: session=" + sessionString);
2117             }
2118             if (mUpdateRequest != UPDATE_MERGE) {
2119                 // Odd, we are not in the midst of merging anything.
2120                 if (DBG) {
2121                     log("callSessionMergeComplete :: no merge in progress.");
2122                 }
2123                 return;
2124             }
2125             // Let's let our parent ImsCall now that we received notification that
2126             // the merge was completed so we can set up our internal state properly
2127             processMergeComplete(session);
2128         }
2129
2130         @Override
2131         public void callSessionMergeFailed(ImsCallSession session, ImsReasonInfo reasonInfo) {
2132             if (DBG) {
2133                 String sessionString = session == null? "null" : session.toString();
2134                 String reasonInfoString = reasonInfo == null ? "null" : reasonInfo.toString();
2135                 log("callSessionMergeFailed :: session=" + sessionString +
2136                         ", reasonInfo=" + reasonInfoString);
2137             }
2138             if (mUpdateRequest != UPDATE_MERGE) {
2139                 // Odd, we are not in the midst of merging anything.
2140                 if (DBG) {
2141                     log("callSessionMergeFailed :: no merge in progress.");
2142                 }
2143                 return;
2144             }
2145             // Let's tell our parent ImsCall that the merge has failed and we need to clean
2146             // up any temporary, transient state.
2147             processMergeFailed(session, reasonInfo);
2148         }
2149
2150         @Override
2151         public void callSessionUpdated(ImsCallSession session, ImsCallProfile profile) {
2152             if (isTransientConferenceSession(session)) {
2153                 log("callSessionUpdated :: not supported for conference session=" + session);
2154                 return;
2155             }
2156
2157             if (DBG) {
2158                 log("callSessionUpdated :: session=" + session + ", profile=" + profile);
2159             }
2160
2161             ImsCall.Listener listener;
2162
2163             synchronized(ImsCall.this) {
2164                 listener = mListener;
2165                 mCallProfile = profile;
2166                 mUpdateRequest = UPDATE_NONE;
2167             }
2168
2169             if (listener != null) {
2170                 try {
2171                     listener.onCallUpdated(ImsCall.this);
2172                 } catch (Throwable t) {
2173                     loge("callSessionUpdated :: ", t);
2174                 }
2175             }
2176         }
2177
2178         @Override
2179         public void callSessionUpdateFailed(ImsCallSession session, ImsReasonInfo reasonInfo) {
2180             if (isTransientConferenceSession(session)) {
2181                 log("callSessionUpdateFailed :: not supported for conference session=" + session);
2182                 return;
2183             }
2184
2185             if (DBG) {
2186                 log("callSessionUpdateFailed :: session=" + session +
2187                         ", reasonInfo=" + reasonInfo);
2188             }
2189
2190             ImsCall.Listener listener;
2191
2192             synchronized(ImsCall.this) {
2193                 listener = mListener;
2194                 mUpdateRequest = UPDATE_NONE;
2195             }
2196
2197             if (listener != null) {
2198                 try {
2199                     listener.onCallUpdateFailed(ImsCall.this, reasonInfo);
2200                 } catch (Throwable t) {
2201                     loge("callSessionUpdateFailed :: ", t);
2202                 }
2203             }
2204         }
2205
2206         @Override
2207         public void callSessionUpdateReceived(ImsCallSession session, ImsCallProfile profile) {
2208             if (isTransientConferenceSession(session)) {
2209                 log("callSessionUpdateReceived :: not supported for conference session=" + session);
2210                 return;
2211             }
2212
2213             if (DBG) {
2214                 log("callSessionUpdateReceived :: session=" + session +
2215                         ", profile=" + profile);
2216             }
2217
2218             ImsCall.Listener listener;
2219
2220             synchronized(ImsCall.this) {
2221                 listener = mListener;
2222                 mProposedCallProfile = profile;
2223                 mUpdateRequest = UPDATE_UNSPECIFIED;
2224             }
2225
2226             if (listener != null) {
2227                 try {
2228                     listener.onCallUpdateReceived(ImsCall.this);
2229                 } catch (Throwable t) {
2230                     loge("callSessionUpdateReceived :: ", t);
2231                 }
2232             }
2233         }
2234
2235         @Override
2236         public void callSessionConferenceExtended(ImsCallSession session, ImsCallSession newSession,
2237                 ImsCallProfile profile) {
2238             if (isTransientConferenceSession(session)) {
2239                 log("callSessionConferenceExtended :: not supported for conference session=" +
2240                         session);
2241                 return;
2242             }
2243
2244             if (DBG) {
2245                 log("callSessionConferenceExtended :: session=" + session
2246                         + ", newSession=" + newSession + ", profile=" + profile);
2247             }
2248
2249             ImsCall newCall = createNewCall(newSession, profile);
2250
2251             if (newCall == null) {
2252                 callSessionConferenceExtendFailed(session, new ImsReasonInfo());
2253                 return;
2254             }
2255
2256             ImsCall.Listener listener;
2257
2258             synchronized(ImsCall.this) {
2259                 listener = mListener;
2260                 mUpdateRequest = UPDATE_NONE;
2261             }
2262
2263             if (listener != null) {
2264                 try {
2265                     listener.onCallConferenceExtended(ImsCall.this, newCall);
2266                 } catch (Throwable t) {
2267                     loge("callSessionConferenceExtended :: ", t);
2268                 }
2269             }
2270         }
2271
2272         @Override
2273         public void callSessionConferenceExtendFailed(ImsCallSession session,
2274                 ImsReasonInfo reasonInfo) {
2275             if (isTransientConferenceSession(session)) {
2276                 log("callSessionConferenceExtendFailed :: not supported for conference session=" +
2277                         session);
2278                 return;
2279             }
2280
2281             if (DBG) {
2282                 log("callSessionConferenceExtendFailed :: session=" + session +
2283                         ", reasonInfo=" + reasonInfo);
2284             }
2285
2286             ImsCall.Listener listener;
2287
2288             synchronized(ImsCall.this) {
2289                 listener = mListener;
2290                 mUpdateRequest = UPDATE_NONE;
2291             }
2292
2293             if (listener != null) {
2294                 try {
2295                     listener.onCallConferenceExtendFailed(ImsCall.this, reasonInfo);
2296                 } catch (Throwable t) {
2297                     loge("callSessionConferenceExtendFailed :: ", t);
2298                 }
2299             }
2300         }
2301
2302         @Override
2303         public void callSessionConferenceExtendReceived(ImsCallSession session,
2304                 ImsCallSession newSession, ImsCallProfile profile) {
2305             if (isTransientConferenceSession(session)) {
2306                 log("callSessionConferenceExtendReceived :: not supported for conference session=" +
2307                         session);
2308                 return;
2309             }
2310
2311             if (DBG) {
2312                 log("callSessionConferenceExtendReceived :: session=" + session
2313                         + ", newSession=" + newSession + ", profile=" + profile);
2314             }
2315
2316             ImsCall newCall = createNewCall(newSession, profile);
2317
2318             if (newCall == null) {
2319                 // Should all the calls be terminated...???
2320                 return;
2321             }
2322
2323             ImsCall.Listener listener;
2324
2325             synchronized(ImsCall.this) {
2326                 listener = mListener;
2327             }
2328
2329             if (listener != null) {
2330                 try {
2331                     listener.onCallConferenceExtendReceived(ImsCall.this, newCall);
2332                 } catch (Throwable t) {
2333                     loge("callSessionConferenceExtendReceived :: ", t);
2334                 }
2335             }
2336         }
2337
2338         @Override
2339         public void callSessionInviteParticipantsRequestDelivered(ImsCallSession session) {
2340             if (isTransientConferenceSession(session)) {
2341                 log("callSessionInviteParticipantsRequestDelivered :: not supported for " +
2342                         "conference session=" + session);
2343                 return;
2344             }
2345
2346             if (DBG) {
2347                 log("callSessionInviteParticipantsRequestDelivered :: session=" + session);
2348             }
2349
2350             ImsCall.Listener listener;
2351
2352             synchronized(ImsCall.this) {
2353                 listener = mListener;
2354             }
2355
2356             if (listener != null) {
2357                 try {
2358                     listener.onCallInviteParticipantsRequestDelivered(ImsCall.this);
2359                 } catch (Throwable t) {
2360                     loge("callSessionInviteParticipantsRequestDelivered :: ", t);
2361                 }
2362             }
2363         }
2364
2365         @Override
2366         public void callSessionInviteParticipantsRequestFailed(ImsCallSession session,
2367                 ImsReasonInfo reasonInfo) {
2368             if (isTransientConferenceSession(session)) {
2369                 log("callSessionInviteParticipantsRequestFailed :: not supported for " +
2370                         "conference session=" + session);
2371                 return;
2372             }
2373
2374             if (DBG) {
2375                 log("callSessionInviteParticipantsRequestFailed :: session=" + session
2376                         + ", reasonInfo=" + reasonInfo);
2377             }
2378
2379             ImsCall.Listener listener;
2380
2381             synchronized(ImsCall.this) {
2382                 listener = mListener;
2383             }
2384
2385             if (listener != null) {
2386                 try {
2387                     listener.onCallInviteParticipantsRequestFailed(ImsCall.this, reasonInfo);
2388                 } catch (Throwable t) {
2389                     loge("callSessionInviteParticipantsRequestFailed :: ", t);
2390                 }
2391             }
2392         }
2393
2394         @Override
2395         public void callSessionRemoveParticipantsRequestDelivered(ImsCallSession session) {
2396             if (isTransientConferenceSession(session)) {
2397                 log("callSessionRemoveParticipantsRequestDelivered :: not supported for " +
2398                         "conference session=" + session);
2399                 return;
2400             }
2401
2402             if (DBG) {
2403                 log("callSessionRemoveParticipantsRequestDelivered :: session=" + session);
2404             }
2405
2406             ImsCall.Listener listener;
2407
2408             synchronized(ImsCall.this) {
2409                 listener = mListener;
2410             }
2411
2412             if (listener != null) {
2413                 try {
2414                     listener.onCallRemoveParticipantsRequestDelivered(ImsCall.this);
2415                 } catch (Throwable t) {
2416                     loge("callSessionRemoveParticipantsRequestDelivered :: ", t);
2417                 }
2418             }
2419         }
2420
2421         @Override
2422         public void callSessionRemoveParticipantsRequestFailed(ImsCallSession session,
2423                 ImsReasonInfo reasonInfo) {
2424             if (isTransientConferenceSession(session)) {
2425                 log("callSessionRemoveParticipantsRequestFailed :: not supported for " +
2426                         "conference session=" +session);
2427                 return;
2428             }
2429
2430             if (DBG) {
2431                 log("callSessionRemoveParticipantsRequestFailed :: session=" + session
2432                         + ", reasonInfo=" + reasonInfo);
2433             }
2434
2435             ImsCall.Listener listener;
2436
2437             synchronized(ImsCall.this) {
2438                 listener = mListener;
2439             }
2440
2441             if (listener != null) {
2442                 try {
2443                     listener.onCallRemoveParticipantsRequestFailed(ImsCall.this, reasonInfo);
2444                 } catch (Throwable t) {
2445                     loge("callSessionRemoveParticipantsRequestFailed :: ", t);
2446                 }
2447             }
2448         }
2449
2450         @Override
2451         public void callSessionConferenceStateUpdated(ImsCallSession session,
2452                 ImsConferenceState state) {
2453             if (isTransientConferenceSession(session)) {
2454                 log("callSessionConferenceStateUpdated :: not supported for conference session=" +
2455                         session);
2456                 return;
2457             }
2458
2459             if (DBG) {
2460                 log("callSessionConferenceStateUpdated :: session=" + session
2461                         + ", state=" + state);
2462             }
2463
2464             conferenceStateUpdated(state);
2465         }
2466
2467         @Override
2468         public void callSessionUssdMessageReceived(ImsCallSession session, int mode,
2469                 String ussdMessage) {
2470             if (isTransientConferenceSession(session)) {
2471                 log("callSessionUssdMessageReceived :: not supported for conference session=" +
2472                         session);
2473                 return;
2474             }
2475
2476             if (DBG) {
2477                 log("callSessionUssdMessageReceived :: session=" + session
2478                         + ", mode=" + mode + ", ussdMessage=" + ussdMessage);
2479             }
2480
2481             ImsCall.Listener listener;
2482
2483             synchronized(ImsCall.this) {
2484                 listener = mListener;
2485             }
2486
2487             if (listener != null) {
2488                 try {
2489                     listener.onCallUssdMessageReceived(ImsCall.this, mode, ussdMessage);
2490                 } catch (Throwable t) {
2491                     loge("callSessionUssdMessageReceived :: ", t);
2492                 }
2493             }
2494         }
2495     }
2496
2497     /**
2498      * Report a new conference state to the current {@link ImsCall} and inform listeners of the
2499      * change.  Marked as {@code VisibleForTesting} so that the
2500      * {@code com.android.internal.telephony.TelephonyTester} class can inject a test conference
2501      * event package into a regular ongoing IMS call.
2502      *
2503      * @param state The {@link ImsConferenceState}.
2504      */
2505     @VisibleForTesting
2506     public void conferenceStateUpdated(ImsConferenceState state) {
2507         Listener listener;
2508
2509         synchronized(this) {
2510             notifyConferenceStateUpdated(state);
2511             listener = mListener;
2512         }
2513
2514         if (listener != null) {
2515             try {
2516                 listener.onCallConferenceStateUpdated(this, state);
2517             } catch (Throwable t) {
2518                 loge("callSessionConferenceStateUpdated :: ", t);
2519             }
2520         }
2521     }
2522 }