ImsPhoneCall.java revision 31b502cb274be3c20b993d51192b56f0e7a9ae09
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.internal.telephony.imsphone;
18
19import android.telephony.Rlog;
20import android.telephony.DisconnectCause;
21
22import com.android.internal.annotations.VisibleForTesting;
23import com.android.internal.telephony.Call;
24import com.android.internal.telephony.CallStateException;
25import com.android.internal.telephony.Connection;
26import com.android.internal.telephony.Phone;
27import com.android.ims.ImsCall;
28import com.android.ims.ImsException;
29import com.android.ims.ImsStreamMediaProfile;
30
31import java.util.List;
32
33/**
34 * {@hide}
35 */
36public class ImsPhoneCall extends Call {
37    /*************************** Instance Variables **************************/
38
39    private static final String LOG_TAG = "ImsPhoneCall";
40
41    /*package*/ ImsPhoneCallTracker mOwner;
42
43    private boolean mRingbackTonePlayed = false;
44
45    /****************************** Constructors *****************************/
46    /*package*/
47    ImsPhoneCall() {
48    }
49
50    /*package*/
51    ImsPhoneCall(ImsPhoneCallTracker owner) {
52        mOwner = owner;
53    }
54
55    public void dispose() {
56        try {
57            mOwner.hangup(this);
58        } catch (CallStateException ex) {
59            //Rlog.e(LOG_TAG, "dispose: unexpected error on hangup", ex);
60            //while disposing, ignore the exception and clean the connections
61        } finally {
62            for(int i = 0, s = mConnections.size(); i < s; i++) {
63                ImsPhoneConnection c = (ImsPhoneConnection) mConnections.get(i);
64                c.onDisconnect(DisconnectCause.LOST_SIGNAL);
65            }
66        }
67    }
68
69    /************************** Overridden from Call *************************/
70
71    @Override
72    public List<Connection>
73    getConnections() {
74        return mConnections;
75    }
76
77    @Override
78    public Phone
79    getPhone() {
80        return mOwner.mPhone;
81    }
82
83    @Override
84    public boolean
85    isMultiparty() {
86        ImsCall imsCall = getImsCall();
87        if (imsCall == null) {
88            return false;
89        }
90
91        return imsCall.isMultiparty();
92    }
93
94    /** Please note: if this is the foreground call and a
95     *  background call exists, the background call will be resumed.
96     */
97    @Override
98    public void
99    hangup() throws CallStateException {
100        mOwner.hangup(this);
101    }
102
103    @Override
104    public String
105    toString() {
106        return mState.toString();
107    }
108
109    //***** Called from ImsPhoneConnection
110
111    /*package*/ void
112    attach(Connection conn) {
113        clearDisconnected();
114        mConnections.add(conn);
115    }
116
117    /*package*/ void
118    attach(Connection conn, State state) {
119        this.attach(conn);
120        mState = state;
121    }
122
123    /*package*/ void
124    attachFake(Connection conn, State state) {
125        attach(conn, state);
126    }
127
128    /**
129     * Called by ImsPhoneConnection when it has disconnected
130     */
131    boolean
132    connectionDisconnected(ImsPhoneConnection conn) {
133        if (mState != State.DISCONNECTED) {
134            /* If only disconnected connections remain, we are disconnected*/
135
136            boolean hasOnlyDisconnectedConnections = true;
137
138            for (int i = 0, s = mConnections.size()  ; i < s; i ++) {
139                if (mConnections.get(i).getState() != State.DISCONNECTED) {
140                    hasOnlyDisconnectedConnections = false;
141                    break;
142                }
143            }
144
145            if (hasOnlyDisconnectedConnections) {
146                mState = State.DISCONNECTED;
147                return true;
148            }
149        }
150
151        return false;
152    }
153
154    /*package*/ void
155    detach(ImsPhoneConnection conn) {
156        mConnections.remove(conn);
157        clearDisconnected();
158    }
159
160    /**
161     * @return true if there's no space in this call for additional
162     * connections to be added via "conference"
163     */
164    /*package*/ boolean
165    isFull() {
166        return mConnections.size() == ImsPhoneCallTracker.MAX_CONNECTIONS_PER_CALL;
167    }
168
169    //***** Called from ImsPhoneCallTracker
170    /**
171     * Called when this Call is being hung up locally (eg, user pressed "end")
172     */
173    void
174    onHangupLocal() {
175        for (int i = 0, s = mConnections.size(); i < s; i++) {
176            ImsPhoneConnection cn = (ImsPhoneConnection)mConnections.get(i);
177            cn.onHangupLocal();
178        }
179        mState = State.DISCONNECTING;
180    }
181
182    /**
183     * Called when it's time to clean up disconnected Connection objects
184     */
185    void
186    clearDisconnected() {
187        for (int i = mConnections.size() - 1 ; i >= 0 ; i--) {
188            ImsPhoneConnection cn = (ImsPhoneConnection)mConnections.get(i);
189
190            if (cn.getState() == State.DISCONNECTED) {
191                mConnections.remove(i);
192            }
193        }
194
195        if (mConnections.size() == 0) {
196            mState = State.IDLE;
197        }
198    }
199
200    /*package*/ ImsPhoneConnection
201    getFirstConnection() {
202        if (mConnections.size() == 0) return null;
203
204        return (ImsPhoneConnection) mConnections.get(0);
205    }
206
207    /*package*/ void
208    setMute(boolean mute) {
209        ImsCall imsCall = getFirstConnection() == null ?
210                null : getFirstConnection().getImsCall();
211        if (imsCall != null) {
212            try {
213                imsCall.setMute(mute);
214            } catch (ImsException e) {
215                Rlog.e(LOG_TAG, "setMute failed : " + e.getMessage());
216            }
217        }
218    }
219
220    /* package */ void
221    merge(ImsPhoneCall that, State state) {
222        // This call is the conference host and the "that" call is the one being merged in.
223        // Set the connect time to the earliest of this call and the other call.
224        // This ensures that when an ImsConference is started, its start time will be when the first
225        // call in the conference started.
226        long earliestConnectTime = Math.min(getEarliestConnectTime(),
227                that.getEarliestConnectTime());
228        getFirstConnection().setConnectTime(earliestConnectTime);
229
230        ImsPhoneConnection[] cc = that.mConnections.toArray(
231                new ImsPhoneConnection[that.mConnections.size()]);
232        for (ImsPhoneConnection c : cc) {
233            c.update(null, state);
234        }
235    }
236
237    /**
238     * Retrieves the {@link ImsCall} for the current {@link ImsPhoneCall}.
239     * <p>
240     * Marked as {@code VisibleForTesting} so that the
241     * {@link com.android.internal.telephony.TelephonyTester} class can inject a test conference
242     * event package into a regular ongoing IMS call.
243     *
244     * @return The {@link ImsCall}.
245     */
246    @VisibleForTesting
247    public ImsCall
248    getImsCall() {
249        return (getFirstConnection() == null) ? null : getFirstConnection().getImsCall();
250    }
251
252    /*package*/ static boolean isLocalTone(ImsCall imsCall) {
253        if ((imsCall == null) || (imsCall.getCallProfile() == null)
254                || (imsCall.getCallProfile().mMediaProfile == null)) {
255            return false;
256        }
257
258        ImsStreamMediaProfile mediaProfile = imsCall.getCallProfile().mMediaProfile;
259
260        return (mediaProfile.mAudioDirection == ImsStreamMediaProfile.DIRECTION_INACTIVE)
261                ? true : false;
262    }
263
264    /*package*/ boolean
265    update (ImsPhoneConnection conn, ImsCall imsCall, State state) {
266        State newState = state;
267        boolean changed = false;
268
269        //ImsCall.Listener.onCallProgressing can be invoked several times
270        //and ringback tone mode can be changed during the call setup procedure
271        if (state == State.ALERTING) {
272            if (mRingbackTonePlayed && !isLocalTone(imsCall)) {
273                mOwner.mPhone.stopRingbackTone();
274                mRingbackTonePlayed = false;
275            } else if (!mRingbackTonePlayed && isLocalTone(imsCall)) {
276                mOwner.mPhone.startRingbackTone();
277                mRingbackTonePlayed = true;
278            }
279        } else {
280            if (mRingbackTonePlayed) {
281                mOwner.mPhone.stopRingbackTone();
282                mRingbackTonePlayed = false;
283            }
284        }
285
286        if ((newState != mState) && (state != State.DISCONNECTED)) {
287            mState = newState;
288            changed = true;
289        } else if (state == State.DISCONNECTED) {
290            changed = true;
291        }
292
293        return changed;
294    }
295
296    /* package */ ImsPhoneConnection
297    getHandoverConnection() {
298        return (ImsPhoneConnection) getEarliestConnection();
299    }
300
301    void switchWith(ImsPhoneCall that) {
302        synchronized (ImsPhoneCall.class) {
303            ImsPhoneCall tmp = new ImsPhoneCall();
304            tmp.takeOver(this);
305            this.takeOver(that);
306            that.takeOver(tmp);
307        }
308    }
309
310    private void takeOver(ImsPhoneCall that) {
311        mConnections = that.mConnections;
312        mState = that.mState;
313        for (Connection c : mConnections) {
314            ((ImsPhoneConnection) c).changeParent(this);
315        }
316    }
317}
318