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