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