ImsPhoneCall.java revision f9b150b221db15e88b97a0b9c0971ca3c71c0313
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 17package com.android.internal.telephony.imsphone; 18 19import android.telecom.ConferenceParticipant; 20import android.telephony.Rlog; 21import android.telephony.DisconnectCause; 22import android.util.Log; 23 24import com.android.internal.annotations.VisibleForTesting; 25import com.android.internal.telephony.Call; 26import com.android.internal.telephony.CallStateException; 27import com.android.internal.telephony.Connection; 28import com.android.internal.telephony.Phone; 29import com.android.ims.ImsCall; 30import com.android.ims.ImsException; 31import com.android.ims.ImsStreamMediaProfile; 32 33import java.util.List; 34 35/** 36 * {@hide} 37 */ 38public 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 public ImsPhoneCall(ImsPhoneCallTracker owner, String context) { 70 mOwner = owner; 71 mCallContext = context; 72 } 73 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> 92 getConnections() { 93 return mConnections; 94 } 95 96 @Override 97 public Phone 98 getPhone() { 99 return mOwner.mPhone; 100 } 101 102 @Override 103 public boolean 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 118 hangup() throws CallStateException { 119 mOwner.hangup(this); 120 } 121 122 @Override 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 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 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 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 172 public void attachFake(Connection conn, State state) { 173 attach(conn, state); 174 } 175 176 /** 177 * Called by ImsPhoneConnection when it has disconnected 178 */ 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 return true; 195 } 196 } 197 198 return false; 199 } 200 201 public void detach(ImsPhoneConnection conn) { 202 if (VDBG) { 203 Rlog.v(LOG_TAG, "detach : " + mCallContext + " conn = " + conn); 204 } 205 mConnections.remove(conn); 206 clearDisconnected(); 207 208 mOwner.logState(); 209 } 210 211 /** 212 * @return true if there's no space in this call for additional 213 * connections to be added via "conference" 214 */ 215 /*package*/ boolean 216 isFull() { 217 return mConnections.size() == ImsPhoneCallTracker.MAX_CONNECTIONS_PER_CALL; 218 } 219 220 //***** Called from ImsPhoneCallTracker 221 /** 222 * Called when this Call is being hung up locally (eg, user pressed "end") 223 */ 224 void 225 onHangupLocal() { 226 for (int i = 0, s = mConnections.size(); i < s; i++) { 227 ImsPhoneConnection cn = (ImsPhoneConnection)mConnections.get(i); 228 cn.onHangupLocal(); 229 } 230 mState = State.DISCONNECTING; 231 } 232 233 /*package*/ ImsPhoneConnection 234 getFirstConnection() { 235 if (mConnections.size() == 0) return null; 236 237 return (ImsPhoneConnection) mConnections.get(0); 238 } 239 240 /*package*/ void 241 setMute(boolean mute) { 242 ImsCall imsCall = getFirstConnection() == null ? 243 null : getFirstConnection().getImsCall(); 244 if (imsCall != null) { 245 try { 246 imsCall.setMute(mute); 247 } catch (ImsException e) { 248 Rlog.e(LOG_TAG, "setMute failed : " + e.getMessage()); 249 } 250 } 251 } 252 253 /* package */ void 254 merge(ImsPhoneCall that, State state) { 255 // This call is the conference host and the "that" call is the one being merged in. 256 // Set the connect time for the conference; this will have been determined when the 257 // conference was initially created. 258 ImsPhoneConnection imsPhoneConnection = getFirstConnection(); 259 if (imsPhoneConnection != null) { 260 long conferenceConnectTime = imsPhoneConnection.getConferenceConnectTime(); 261 if (conferenceConnectTime > 0) { 262 imsPhoneConnection.setConnectTime(conferenceConnectTime); 263 } else { 264 if (DBG) { 265 Rlog.d(LOG_TAG, "merge: conference connect time is 0"); 266 } 267 } 268 } 269 if (DBG) { 270 Rlog.d(LOG_TAG, "merge(" + mCallContext + "): " + that + "state = " 271 + state); 272 } 273 } 274 275 /** 276 * Retrieves the {@link ImsCall} for the current {@link ImsPhoneCall}. 277 * <p> 278 * Marked as {@code VisibleForTesting} so that the 279 * {@link com.android.internal.telephony.TelephonyTester} class can inject a test conference 280 * event package into a regular ongoing IMS call. 281 * 282 * @return The {@link ImsCall}. 283 */ 284 @VisibleForTesting 285 public ImsCall 286 getImsCall() { 287 return (getFirstConnection() == null) ? null : getFirstConnection().getImsCall(); 288 } 289 290 /*package*/ static boolean isLocalTone(ImsCall imsCall) { 291 if ((imsCall == null) || (imsCall.getCallProfile() == null) 292 || (imsCall.getCallProfile().mMediaProfile == null)) { 293 return false; 294 } 295 296 ImsStreamMediaProfile mediaProfile = imsCall.getCallProfile().mMediaProfile; 297 298 return (mediaProfile.mAudioDirection == ImsStreamMediaProfile.DIRECTION_INACTIVE) 299 ? true : false; 300 } 301 302 public boolean update (ImsPhoneConnection conn, ImsCall imsCall, State state) { 303 State newState = state; 304 boolean changed = false; 305 306 //ImsCall.Listener.onCallProgressing can be invoked several times 307 //and ringback tone mode can be changed during the call setup procedure 308 if (state == State.ALERTING) { 309 if (mRingbackTonePlayed && !isLocalTone(imsCall)) { 310 mOwner.mPhone.stopRingbackTone(); 311 mRingbackTonePlayed = false; 312 } else if (!mRingbackTonePlayed && isLocalTone(imsCall)) { 313 mOwner.mPhone.startRingbackTone(); 314 mRingbackTonePlayed = true; 315 } 316 } else { 317 if (mRingbackTonePlayed) { 318 mOwner.mPhone.stopRingbackTone(); 319 mRingbackTonePlayed = false; 320 } 321 } 322 323 if ((newState != mState) && (state != State.DISCONNECTED)) { 324 mState = newState; 325 changed = true; 326 } else if (state == State.DISCONNECTED) { 327 changed = true; 328 } 329 330 return changed; 331 } 332 333 /* package */ ImsPhoneConnection 334 getHandoverConnection() { 335 return (ImsPhoneConnection) getEarliestConnection(); 336 } 337 338 public void switchWith(ImsPhoneCall that) { 339 if (VDBG) { 340 Rlog.v(LOG_TAG, "switchWith : switchCall = " + this + " withCall = " + that); 341 } 342 synchronized (ImsPhoneCall.class) { 343 ImsPhoneCall tmp = new ImsPhoneCall(); 344 tmp.takeOver(this); 345 this.takeOver(that); 346 that.takeOver(tmp); 347 } 348 mOwner.logState(); 349 } 350 351 private void takeOver(ImsPhoneCall that) { 352 mConnections = that.mConnections; 353 mState = that.mState; 354 for (Connection c : mConnections) { 355 ((ImsPhoneConnection) c).changeParent(this); 356 } 357 } 358} 359