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