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 android.telephony.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*/ ImsPhoneCall()65 ImsPhoneCall() { 66 mCallContext = CONTEXT_UNKNOWN; 67 } 68 ImsPhoneCall(ImsPhoneCallTracker owner, String context)69 public ImsPhoneCall(ImsPhoneCallTracker owner, String context) { 70 mOwner = owner; 71 mCallContext = context; 72 } 73 dispose()74 public void dispose() { 75 try { 76 mOwner.hangup(this); 77 } catch (CallStateException ex) { 78 //Rlog.e(LOG_TAG, "dispose: unexpected error on hangup", ex); 79 //while disposing, ignore the exception and clean the connections 80 } finally { 81 for(int i = 0, s = mConnections.size(); i < s; i++) { 82 ImsPhoneConnection c = (ImsPhoneConnection) mConnections.get(i); 83 c.onDisconnect(DisconnectCause.LOST_SIGNAL); 84 } 85 } 86 } 87 88 /************************** Overridden from Call *************************/ 89 90 @Override 91 public List<Connection> getConnections()92 getConnections() { 93 return mConnections; 94 } 95 96 @Override 97 public Phone getPhone()98 getPhone() { 99 return mOwner.mPhone; 100 } 101 102 @Override 103 public boolean isMultiparty()104 isMultiparty() { 105 ImsCall imsCall = getImsCall(); 106 if (imsCall == null) { 107 return false; 108 } 109 110 return imsCall.isMultiparty(); 111 } 112 113 /** Please note: if this is the foreground call and a 114 * background call exists, the background call will be resumed. 115 */ 116 @Override 117 public void hangup()118 hangup() throws CallStateException { 119 mOwner.hangup(this); 120 } 121 122 @Override toString()123 public String toString() { 124 StringBuilder sb = new StringBuilder(); 125 sb.append("[ImsPhoneCall "); 126 sb.append(mCallContext); 127 sb.append(" state: "); 128 sb.append(mState.toString()); 129 sb.append(" "); 130 if (mConnections.size() > 1) { 131 sb.append(" ERROR_MULTIPLE "); 132 } 133 for (Connection conn : mConnections) { 134 sb.append(conn); 135 sb.append(" "); 136 } 137 138 sb.append("]"); 139 return sb.toString(); 140 } 141 142 @Override getConferenceParticipants()143 public List<ConferenceParticipant> getConferenceParticipants() { 144 ImsCall call = getImsCall(); 145 if (call == null) { 146 return null; 147 } 148 return call.getConferenceParticipants(); 149 } 150 151 //***** Called from ImsPhoneConnection 152 attach(Connection conn)153 public void attach(Connection conn) { 154 if (VDBG) { 155 Rlog.v(LOG_TAG, "attach : " + mCallContext + " conn = " + conn); 156 } 157 clearDisconnected(); 158 mConnections.add(conn); 159 160 mOwner.logState(); 161 } 162 attach(Connection conn, State state)163 public void attach(Connection conn, State state) { 164 if (VDBG) { 165 Rlog.v(LOG_TAG, "attach : " + mCallContext + " state = " + 166 state.toString()); 167 } 168 this.attach(conn); 169 mState = state; 170 } 171 attachFake(Connection conn, State state)172 public void attachFake(Connection conn, State state) { 173 attach(conn, state); 174 } 175 176 /** 177 * Called by ImsPhoneConnection when it has disconnected 178 */ connectionDisconnected(ImsPhoneConnection conn)179 public boolean connectionDisconnected(ImsPhoneConnection conn) { 180 if (mState != State.DISCONNECTED) { 181 /* If only disconnected connections remain, we are disconnected*/ 182 183 boolean hasOnlyDisconnectedConnections = true; 184 185 for (int i = 0, s = mConnections.size() ; i < s; i ++) { 186 if (mConnections.get(i).getState() != State.DISCONNECTED) { 187 hasOnlyDisconnectedConnections = false; 188 break; 189 } 190 } 191 192 if (hasOnlyDisconnectedConnections) { 193 mState = State.DISCONNECTED; 194 if (VDBG) { 195 Rlog.v(LOG_TAG, "connectionDisconnected : " + mCallContext + " state = " + 196 mState); 197 } 198 return true; 199 } 200 } 201 202 return false; 203 } 204 detach(ImsPhoneConnection conn)205 public void detach(ImsPhoneConnection conn) { 206 if (VDBG) { 207 Rlog.v(LOG_TAG, "detach : " + mCallContext + " conn = " + conn); 208 } 209 mConnections.remove(conn); 210 clearDisconnected(); 211 212 mOwner.logState(); 213 } 214 215 /** 216 * @return true if there's no space in this call for additional 217 * connections to be added via "conference" 218 */ 219 /*package*/ boolean isFull()220 isFull() { 221 return mConnections.size() == ImsPhoneCallTracker.MAX_CONNECTIONS_PER_CALL; 222 } 223 224 //***** Called from ImsPhoneCallTracker 225 /** 226 * Called when this Call is being hung up locally (eg, user pressed "end") 227 */ 228 void onHangupLocal()229 onHangupLocal() { 230 for (int i = 0, s = mConnections.size(); i < s; i++) { 231 ImsPhoneConnection cn = (ImsPhoneConnection)mConnections.get(i); 232 cn.onHangupLocal(); 233 } 234 mState = State.DISCONNECTING; 235 if (VDBG) { 236 Rlog.v(LOG_TAG, "onHangupLocal : " + mCallContext + " state = " + mState); 237 } 238 } 239 240 @VisibleForTesting getFirstConnection()241 public ImsPhoneConnection getFirstConnection() { 242 if (mConnections.size() == 0) return null; 243 244 return (ImsPhoneConnection) mConnections.get(0); 245 } 246 247 /*package*/ void setMute(boolean mute)248 setMute(boolean mute) { 249 ImsCall imsCall = getFirstConnection() == null ? 250 null : getFirstConnection().getImsCall(); 251 if (imsCall != null) { 252 try { 253 imsCall.setMute(mute); 254 } catch (ImsException e) { 255 Rlog.e(LOG_TAG, "setMute failed : " + e.getMessage()); 256 } 257 } 258 } 259 260 /* package */ void merge(ImsPhoneCall that, State state)261 merge(ImsPhoneCall that, State state) { 262 // This call is the conference host and the "that" call is the one being merged in. 263 // Set the connect time for the conference; this will have been determined when the 264 // conference was initially created. 265 ImsPhoneConnection imsPhoneConnection = getFirstConnection(); 266 if (imsPhoneConnection != null) { 267 long conferenceConnectTime = imsPhoneConnection.getConferenceConnectTime(); 268 if (conferenceConnectTime > 0) { 269 imsPhoneConnection.setConnectTime(conferenceConnectTime); 270 imsPhoneConnection.setConnectTimeReal(imsPhoneConnection.getConnectTimeReal()); 271 } else { 272 if (DBG) { 273 Rlog.d(LOG_TAG, "merge: conference connect time is 0"); 274 } 275 } 276 } 277 if (DBG) { 278 Rlog.d(LOG_TAG, "merge(" + mCallContext + "): " + that + "state = " 279 + state); 280 } 281 } 282 283 /** 284 * Retrieves the {@link ImsCall} for the current {@link ImsPhoneCall}. 285 * <p> 286 * Marked as {@code VisibleForTesting} so that the 287 * {@link com.android.internal.telephony.TelephonyTester} class can inject a test conference 288 * event package into a regular ongoing IMS call. 289 * 290 * @return The {@link ImsCall}. 291 */ 292 @VisibleForTesting 293 public ImsCall getImsCall()294 getImsCall() { 295 return (getFirstConnection() == null) ? null : getFirstConnection().getImsCall(); 296 } 297 isLocalTone(ImsCall imsCall)298 /*package*/ static boolean isLocalTone(ImsCall imsCall) { 299 if ((imsCall == null) || (imsCall.getCallProfile() == null) 300 || (imsCall.getCallProfile().mMediaProfile == null)) { 301 return false; 302 } 303 304 ImsStreamMediaProfile mediaProfile = imsCall.getCallProfile().mMediaProfile; 305 306 return (mediaProfile.mAudioDirection == ImsStreamMediaProfile.DIRECTION_INACTIVE) 307 ? true : false; 308 } 309 update(ImsPhoneConnection conn, ImsCall imsCall, State state)310 public boolean update (ImsPhoneConnection conn, ImsCall imsCall, State state) { 311 boolean changed = false; 312 State oldState = mState; 313 314 //ImsCall.Listener.onCallProgressing can be invoked several times 315 //and ringback tone mode can be changed during the call setup procedure 316 if (state == State.ALERTING) { 317 if (mRingbackTonePlayed && !isLocalTone(imsCall)) { 318 mOwner.mPhone.stopRingbackTone(); 319 mRingbackTonePlayed = false; 320 } else if (!mRingbackTonePlayed && isLocalTone(imsCall)) { 321 mOwner.mPhone.startRingbackTone(); 322 mRingbackTonePlayed = true; 323 } 324 } else { 325 if (mRingbackTonePlayed) { 326 mOwner.mPhone.stopRingbackTone(); 327 mRingbackTonePlayed = false; 328 } 329 } 330 331 if ((state != mState) && (state != State.DISCONNECTED)) { 332 mState = state; 333 changed = true; 334 } else if (state == State.DISCONNECTED) { 335 changed = true; 336 } 337 338 if (VDBG) { 339 Rlog.v(LOG_TAG, "update : " + mCallContext + " state: " + oldState + " --> " + mState); 340 } 341 342 return changed; 343 } 344 345 /* package */ ImsPhoneConnection getHandoverConnection()346 getHandoverConnection() { 347 return (ImsPhoneConnection) getEarliestConnection(); 348 } 349 switchWith(ImsPhoneCall that)350 public void switchWith(ImsPhoneCall that) { 351 if (VDBG) { 352 Rlog.v(LOG_TAG, "switchWith : switchCall = " + this + " withCall = " + that); 353 } 354 synchronized (ImsPhoneCall.class) { 355 ImsPhoneCall tmp = new ImsPhoneCall(); 356 tmp.takeOver(this); 357 this.takeOver(that); 358 that.takeOver(tmp); 359 } 360 mOwner.logState(); 361 } 362 takeOver(ImsPhoneCall that)363 private void takeOver(ImsPhoneCall that) { 364 mConnections = that.mConnections; 365 mState = that.mState; 366 for (Connection c : mConnections) { 367 ((ImsPhoneConnection) c).changeParent(this); 368 } 369 } 370 } 371