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