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