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