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