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