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