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