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