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.telephony.Rlog; 20 import android.telephony.DisconnectCause; 21 22 import com.android.internal.annotations.VisibleForTesting; 23 import com.android.internal.telephony.Call; 24 import com.android.internal.telephony.CallStateException; 25 import com.android.internal.telephony.Connection; 26 import com.android.internal.telephony.Phone; 27 import com.android.ims.ImsCall; 28 import com.android.ims.ImsException; 29 import com.android.ims.ImsStreamMediaProfile; 30 31 import java.util.List; 32 33 /** 34 * {@hide} 35 */ 36 public class ImsPhoneCall extends Call { 37 /*************************** Instance Variables **************************/ 38 39 private static final String LOG_TAG = "ImsPhoneCall"; 40 41 /*package*/ ImsPhoneCallTracker mOwner; 42 43 private boolean mRingbackTonePlayed = false; 44 45 /****************************** Constructors *****************************/ 46 /*package*/ 47 ImsPhoneCall() { 48 } 49 50 /*package*/ 51 ImsPhoneCall(ImsPhoneCallTracker owner) { 52 mOwner = owner; 53 } 54 55 public void dispose() { 56 try { 57 mOwner.hangup(this); 58 } catch (CallStateException ex) { 59 //Rlog.e(LOG_TAG, "dispose: unexpected error on hangup", ex); 60 //while disposing, ignore the exception and clean the connections 61 } finally { 62 for(int i = 0, s = mConnections.size(); i < s; i++) { 63 ImsPhoneConnection c = (ImsPhoneConnection) mConnections.get(i); 64 c.onDisconnect(DisconnectCause.LOST_SIGNAL); 65 } 66 } 67 } 68 69 /************************** Overridden from Call *************************/ 70 71 @Override 72 public List<Connection> 73 getConnections() { 74 return mConnections; 75 } 76 77 @Override 78 public Phone 79 getPhone() { 80 return mOwner.mPhone; 81 } 82 83 @Override 84 public boolean 85 isMultiparty() { 86 ImsCall imsCall = getImsCall(); 87 if (imsCall == null) { 88 return false; 89 } 90 91 return imsCall.isMultiparty(); 92 } 93 94 /** Please note: if this is the foreground call and a 95 * background call exists, the background call will be resumed. 96 */ 97 @Override 98 public void 99 hangup() throws CallStateException { 100 mOwner.hangup(this); 101 } 102 103 @Override 104 public String 105 toString() { 106 return mState.toString(); 107 } 108 109 //***** Called from ImsPhoneConnection 110 111 /*package*/ void 112 attach(Connection conn) { 113 clearDisconnected(); 114 mConnections.add(conn); 115 } 116 117 /*package*/ void 118 attach(Connection conn, State state) { 119 this.attach(conn); 120 mState = state; 121 } 122 123 /*package*/ void 124 attachFake(Connection conn, State state) { 125 attach(conn, state); 126 } 127 128 /** 129 * Called by ImsPhoneConnection when it has disconnected 130 */ 131 boolean 132 connectionDisconnected(ImsPhoneConnection conn) { 133 if (mState != State.DISCONNECTED) { 134 /* If only disconnected connections remain, we are disconnected*/ 135 136 boolean hasOnlyDisconnectedConnections = true; 137 138 for (int i = 0, s = mConnections.size() ; i < s; i ++) { 139 if (mConnections.get(i).getState() != State.DISCONNECTED) { 140 hasOnlyDisconnectedConnections = false; 141 break; 142 } 143 } 144 145 if (hasOnlyDisconnectedConnections) { 146 mState = State.DISCONNECTED; 147 return true; 148 } 149 } 150 151 return false; 152 } 153 154 /*package*/ void 155 detach(ImsPhoneConnection conn) { 156 mConnections.remove(conn); 157 clearDisconnected(); 158 } 159 160 /** 161 * @return true if there's no space in this call for additional 162 * connections to be added via "conference" 163 */ 164 /*package*/ boolean 165 isFull() { 166 return mConnections.size() == ImsPhoneCallTracker.MAX_CONNECTIONS_PER_CALL; 167 } 168 169 //***** Called from ImsPhoneCallTracker 170 /** 171 * Called when this Call is being hung up locally (eg, user pressed "end") 172 */ 173 void 174 onHangupLocal() { 175 for (int i = 0, s = mConnections.size(); i < s; i++) { 176 ImsPhoneConnection cn = (ImsPhoneConnection)mConnections.get(i); 177 cn.onHangupLocal(); 178 } 179 mState = State.DISCONNECTING; 180 } 181 182 /** 183 * Called when it's time to clean up disconnected Connection objects 184 */ 185 void 186 clearDisconnected() { 187 for (int i = mConnections.size() - 1 ; i >= 0 ; i--) { 188 ImsPhoneConnection cn = (ImsPhoneConnection)mConnections.get(i); 189 190 if (cn.getState() == State.DISCONNECTED) { 191 mConnections.remove(i); 192 } 193 } 194 195 if (mConnections.size() == 0) { 196 mState = State.IDLE; 197 } 198 } 199 200 /*package*/ ImsPhoneConnection 201 getFirstConnection() { 202 if (mConnections.size() == 0) return null; 203 204 return (ImsPhoneConnection) mConnections.get(0); 205 } 206 207 /*package*/ void 208 setMute(boolean mute) { 209 ImsCall imsCall = getFirstConnection() == null ? 210 null : getFirstConnection().getImsCall(); 211 if (imsCall != null) { 212 try { 213 imsCall.setMute(mute); 214 } catch (ImsException e) { 215 Rlog.e(LOG_TAG, "setMute failed : " + e.getMessage()); 216 } 217 } 218 } 219 220 /* package */ void 221 merge(ImsPhoneCall that, State state) { 222 // This call is the conference host and the "that" call is the one being merged in. 223 // Set the connect time for the conference; this will have been determined when the 224 // conference was initially created. 225 ImsPhoneConnection imsPhoneConnection = getFirstConnection(); 226 if (imsPhoneConnection != null) { 227 long conferenceConnectTime = imsPhoneConnection.getConferenceConnectTime(); 228 if (conferenceConnectTime > 0) { 229 imsPhoneConnection.setConnectTime(conferenceConnectTime); 230 } 231 } 232 233 ImsPhoneConnection[] cc = that.mConnections.toArray( 234 new ImsPhoneConnection[that.mConnections.size()]); 235 for (ImsPhoneConnection c : cc) { 236 c.update(null, state); 237 } 238 } 239 240 /** 241 * Retrieves the {@link ImsCall} for the current {@link ImsPhoneCall}. 242 * <p> 243 * Marked as {@code VisibleForTesting} so that the 244 * {@link com.android.internal.telephony.TelephonyTester} class can inject a test conference 245 * event package into a regular ongoing IMS call. 246 * 247 * @return The {@link ImsCall}. 248 */ 249 @VisibleForTesting 250 public ImsCall 251 getImsCall() { 252 return (getFirstConnection() == null) ? null : getFirstConnection().getImsCall(); 253 } 254 255 /*package*/ static boolean isLocalTone(ImsCall imsCall) { 256 if ((imsCall == null) || (imsCall.getCallProfile() == null) 257 || (imsCall.getCallProfile().mMediaProfile == null)) { 258 return false; 259 } 260 261 ImsStreamMediaProfile mediaProfile = imsCall.getCallProfile().mMediaProfile; 262 263 return (mediaProfile.mAudioDirection == ImsStreamMediaProfile.DIRECTION_INACTIVE) 264 ? true : false; 265 } 266 267 /*package*/ boolean 268 update (ImsPhoneConnection conn, ImsCall imsCall, State state) { 269 State newState = state; 270 boolean changed = false; 271 272 //ImsCall.Listener.onCallProgressing can be invoked several times 273 //and ringback tone mode can be changed during the call setup procedure 274 if (state == State.ALERTING) { 275 if (mRingbackTonePlayed && !isLocalTone(imsCall)) { 276 mOwner.mPhone.stopRingbackTone(); 277 mRingbackTonePlayed = false; 278 } else if (!mRingbackTonePlayed && isLocalTone(imsCall)) { 279 mOwner.mPhone.startRingbackTone(); 280 mRingbackTonePlayed = true; 281 } 282 } else { 283 if (mRingbackTonePlayed) { 284 mOwner.mPhone.stopRingbackTone(); 285 mRingbackTonePlayed = false; 286 } 287 } 288 289 if ((newState != mState) && (state != State.DISCONNECTED)) { 290 mState = newState; 291 changed = true; 292 } else if (state == State.DISCONNECTED) { 293 changed = true; 294 } 295 296 return changed; 297 } 298 299 /* package */ ImsPhoneConnection 300 getHandoverConnection() { 301 return (ImsPhoneConnection) getEarliestConnection(); 302 } 303 304 void switchWith(ImsPhoneCall that) { 305 synchronized (ImsPhoneCall.class) { 306 ImsPhoneCall tmp = new ImsPhoneCall(); 307 tmp.takeOver(this); 308 this.takeOver(that); 309 that.takeOver(tmp); 310 } 311 } 312 313 private void takeOver(ImsPhoneCall that) { 314 mConnections = that.mConnections; 315 mState = that.mState; 316 for (Connection c : mConnections) { 317 ((ImsPhoneConnection) c).changeParent(this); 318 } 319 } 320 } 321