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