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