ImsPhoneConnection.java revision 99ea18f0323a53cacf6824db5ec4dd460f9b3798
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.content.Context; 20import android.net.Uri; 21import android.os.AsyncResult; 22import android.os.Bundle; 23import android.os.Handler; 24import android.os.Looper; 25import android.os.Message; 26import android.os.PersistableBundle; 27import android.os.PowerManager; 28import android.os.Registrant; 29import android.os.SystemClock; 30import android.telecom.VideoProfile; 31import android.telephony.CarrierConfigManager; 32import android.telephony.DisconnectCause; 33import android.telephony.PhoneNumberUtils; 34import android.telephony.Rlog; 35import android.telephony.ServiceState; 36import android.text.TextUtils; 37 38import com.android.ims.ImsException; 39import com.android.ims.ImsStreamMediaProfile; 40import com.android.ims.internal.ImsVideoCallProviderWrapper; 41import com.android.internal.telephony.CallStateException; 42import com.android.internal.telephony.Connection; 43import com.android.internal.telephony.Phone; 44import com.android.internal.telephony.PhoneConstants; 45import com.android.internal.telephony.UUSInfo; 46 47import com.android.ims.ImsCall; 48import com.android.ims.ImsCallProfile; 49 50import java.util.Objects; 51 52/** 53 * {@hide} 54 */ 55public class ImsPhoneConnection extends Connection implements 56 ImsVideoCallProviderWrapper.ImsVideoProviderWrapperCallback { 57 58 private static final String LOG_TAG = "ImsPhoneConnection"; 59 private static final boolean DBG = true; 60 61 //***** Instance Variables 62 63 private ImsPhoneCallTracker mOwner; 64 private ImsPhoneCall mParent; 65 private ImsCall mImsCall; 66 private Bundle mExtras = new Bundle(); 67 68 private boolean mDisconnected; 69 70 /* 71 int mIndex; // index in ImsPhoneCallTracker.connections[], -1 if unassigned 72 // The GSM index is 1 + this 73 */ 74 75 /* 76 * These time/timespan values are based on System.currentTimeMillis(), 77 * i.e., "wall clock" time. 78 */ 79 private long mDisconnectTime; 80 81 private UUSInfo mUusInfo; 82 private Handler mHandler; 83 84 private PowerManager.WakeLock mPartialWakeLock; 85 86 // The cached connect time of the connection when it turns into a conference. 87 private long mConferenceConnectTime = 0; 88 89 // The cached delay to be used between DTMF tones fetched from carrier config. 90 private int mDtmfToneDelay = 0; 91 92 private boolean mIsEmergency = false; 93 94 /** 95 * Used to indicate that video state changes detected by 96 * {@link #updateMediaCapabilities(ImsCall)} should be ignored. When a video state change from 97 * unpaused to paused occurs, we set this flag and then update the existing video state when 98 * new {@link #onReceiveSessionModifyResponse(int, VideoProfile, VideoProfile)} callbacks come 99 * in. When the video un-pauses we continue receiving the video state updates. 100 */ 101 private boolean mShouldIgnoreVideoStateChanges = false; 102 103 private ImsVideoCallProviderWrapper mImsVideoCallProviderWrapper; 104 105 private int mPreciseDisconnectCause = 0; 106 107 //***** Event Constants 108 private static final int EVENT_DTMF_DONE = 1; 109 private static final int EVENT_PAUSE_DONE = 2; 110 private static final int EVENT_NEXT_POST_DIAL = 3; 111 private static final int EVENT_WAKE_LOCK_TIMEOUT = 4; 112 private static final int EVENT_DTMF_DELAY_DONE = 5; 113 114 //***** Constants 115 private static final int PAUSE_DELAY_MILLIS = 3 * 1000; 116 private static final int WAKE_LOCK_TIMEOUT_MILLIS = 60*1000; 117 118 //***** Inner Classes 119 120 class MyHandler extends Handler { 121 MyHandler(Looper l) {super(l);} 122 123 @Override 124 public void 125 handleMessage(Message msg) { 126 127 switch (msg.what) { 128 case EVENT_NEXT_POST_DIAL: 129 case EVENT_DTMF_DELAY_DONE: 130 case EVENT_PAUSE_DONE: 131 processNextPostDialChar(); 132 break; 133 case EVENT_WAKE_LOCK_TIMEOUT: 134 releaseWakeLock(); 135 break; 136 case EVENT_DTMF_DONE: 137 // We may need to add a delay specified by carrier between DTMF tones that are 138 // sent out. 139 mHandler.sendMessageDelayed(mHandler.obtainMessage(EVENT_DTMF_DELAY_DONE), 140 mDtmfToneDelay); 141 break; 142 } 143 } 144 } 145 146 //***** Constructors 147 148 /** This is probably an MT call */ 149 public ImsPhoneConnection(Phone phone, ImsCall imsCall, ImsPhoneCallTracker ct, 150 ImsPhoneCall parent, boolean isUnknown) { 151 super(PhoneConstants.PHONE_TYPE_IMS); 152 createWakeLock(phone.getContext()); 153 acquireWakeLock(); 154 155 mOwner = ct; 156 mHandler = new MyHandler(mOwner.getLooper()); 157 mImsCall = imsCall; 158 159 if ((imsCall != null) && (imsCall.getCallProfile() != null)) { 160 mAddress = imsCall.getCallProfile().getCallExtra(ImsCallProfile.EXTRA_OI); 161 mCnapName = imsCall.getCallProfile().getCallExtra(ImsCallProfile.EXTRA_CNA); 162 mNumberPresentation = ImsCallProfile.OIRToPresentation( 163 imsCall.getCallProfile().getCallExtraInt(ImsCallProfile.EXTRA_OIR)); 164 mCnapNamePresentation = ImsCallProfile.OIRToPresentation( 165 imsCall.getCallProfile().getCallExtraInt(ImsCallProfile.EXTRA_CNAP)); 166 updateMediaCapabilities(imsCall); 167 } else { 168 mNumberPresentation = PhoneConstants.PRESENTATION_UNKNOWN; 169 mCnapNamePresentation = PhoneConstants.PRESENTATION_UNKNOWN; 170 } 171 172 mIsIncoming = !isUnknown; 173 mCreateTime = System.currentTimeMillis(); 174 mUusInfo = null; 175 176 // Ensure any extras set on the ImsCallProfile at the start of the call are cached locally 177 // in the ImsPhoneConnection. This isn't going to inform any listeners (since the original 178 // connection is not likely to be associated with a TelephonyConnection yet). 179 updateExtras(imsCall); 180 181 mParent = parent; 182 mParent.attach(this, 183 (mIsIncoming? ImsPhoneCall.State.INCOMING: ImsPhoneCall.State.DIALING)); 184 185 fetchDtmfToneDelay(phone); 186 187 if (phone.getContext().getResources().getBoolean( 188 com.android.internal.R.bool.config_use_voip_mode_for_ims)) { 189 setAudioModeIsVoip(true); 190 } 191 } 192 193 /** This is an MO call, created when dialing */ 194 public ImsPhoneConnection(Phone phone, String dialString, ImsPhoneCallTracker ct, 195 ImsPhoneCall parent, boolean isEmergency) { 196 super(PhoneConstants.PHONE_TYPE_IMS); 197 createWakeLock(phone.getContext()); 198 acquireWakeLock(); 199 200 mOwner = ct; 201 mHandler = new MyHandler(mOwner.getLooper()); 202 203 mDialString = dialString; 204 205 mAddress = PhoneNumberUtils.extractNetworkPortionAlt(dialString); 206 mPostDialString = PhoneNumberUtils.extractPostDialPortion(dialString); 207 208 //mIndex = -1; 209 210 mIsIncoming = false; 211 mCnapName = null; 212 mCnapNamePresentation = PhoneConstants.PRESENTATION_ALLOWED; 213 mNumberPresentation = PhoneConstants.PRESENTATION_ALLOWED; 214 mCreateTime = System.currentTimeMillis(); 215 216 mParent = parent; 217 parent.attachFake(this, ImsPhoneCall.State.DIALING); 218 219 mIsEmergency = isEmergency; 220 221 fetchDtmfToneDelay(phone); 222 223 if (phone.getContext().getResources().getBoolean( 224 com.android.internal.R.bool.config_use_voip_mode_for_ims)) { 225 setAudioModeIsVoip(true); 226 } 227 } 228 229 public void dispose() { 230 } 231 232 static boolean 233 equalsHandlesNulls (Object a, Object b) { 234 return (a == null) ? (b == null) : a.equals (b); 235 } 236 237 private static int applyLocalCallCapabilities(ImsCallProfile localProfile, int capabilities) { 238 Rlog.w(LOG_TAG, "applyLocalCallCapabilities - localProfile = "+localProfile); 239 capabilities = removeCapability(capabilities, 240 Connection.Capability.SUPPORTS_VT_LOCAL_BIDIRECTIONAL); 241 242 switch (localProfile.mCallType) { 243 case ImsCallProfile.CALL_TYPE_VT: 244 // Fall-through 245 case ImsCallProfile.CALL_TYPE_VIDEO_N_VOICE: 246 capabilities = addCapability(capabilities, 247 Connection.Capability.SUPPORTS_VT_LOCAL_BIDIRECTIONAL); 248 break; 249 } 250 return capabilities; 251 } 252 253 private static int applyRemoteCallCapabilities(ImsCallProfile remoteProfile, int capabilities) { 254 Rlog.w(LOG_TAG, "applyRemoteCallCapabilities - remoteProfile = "+remoteProfile); 255 capabilities = removeCapability(capabilities, 256 Connection.Capability.SUPPORTS_VT_REMOTE_BIDIRECTIONAL); 257 258 switch (remoteProfile.mCallType) { 259 case ImsCallProfile.CALL_TYPE_VT: 260 // fall-through 261 case ImsCallProfile.CALL_TYPE_VIDEO_N_VOICE: 262 capabilities = addCapability(capabilities, 263 Connection.Capability.SUPPORTS_VT_REMOTE_BIDIRECTIONAL); 264 break; 265 } 266 return capabilities; 267 } 268 269 @Override 270 public String getOrigDialString(){ 271 return mDialString; 272 } 273 274 @Override 275 public ImsPhoneCall getCall() { 276 return mParent; 277 } 278 279 @Override 280 public long getDisconnectTime() { 281 return mDisconnectTime; 282 } 283 284 @Override 285 public long getHoldingStartTime() { 286 return mHoldingStartTime; 287 } 288 289 @Override 290 public long getHoldDurationMillis() { 291 if (getState() != ImsPhoneCall.State.HOLDING) { 292 // If not holding, return 0 293 return 0; 294 } else { 295 return SystemClock.elapsedRealtime() - mHoldingStartTime; 296 } 297 } 298 299 public void setDisconnectCause(int cause) { 300 mCause = cause; 301 } 302 303 @Override 304 public String getVendorDisconnectCause() { 305 return null; 306 } 307 308 public ImsPhoneCallTracker getOwner () { 309 return mOwner; 310 } 311 312 @Override 313 public ImsPhoneCall.State getState() { 314 if (mDisconnected) { 315 return ImsPhoneCall.State.DISCONNECTED; 316 } else { 317 return super.getState(); 318 } 319 } 320 321 @Override 322 public void hangup() throws CallStateException { 323 if (!mDisconnected) { 324 mOwner.hangup(this); 325 } else { 326 throw new CallStateException ("disconnected"); 327 } 328 } 329 330 @Override 331 public void separate() throws CallStateException { 332 throw new CallStateException ("not supported"); 333 } 334 335 @Override 336 public void proceedAfterWaitChar() { 337 if (mPostDialState != PostDialState.WAIT) { 338 Rlog.w(LOG_TAG, "ImsPhoneConnection.proceedAfterWaitChar(): Expected " 339 + "getPostDialState() to be WAIT but was " + mPostDialState); 340 return; 341 } 342 343 setPostDialState(PostDialState.STARTED); 344 345 processNextPostDialChar(); 346 } 347 348 @Override 349 public void proceedAfterWildChar(String str) { 350 if (mPostDialState != PostDialState.WILD) { 351 Rlog.w(LOG_TAG, "ImsPhoneConnection.proceedAfterWaitChar(): Expected " 352 + "getPostDialState() to be WILD but was " + mPostDialState); 353 return; 354 } 355 356 setPostDialState(PostDialState.STARTED); 357 358 // make a new postDialString, with the wild char replacement string 359 // at the beginning, followed by the remaining postDialString. 360 361 StringBuilder buf = new StringBuilder(str); 362 buf.append(mPostDialString.substring(mNextPostDialChar)); 363 mPostDialString = buf.toString(); 364 mNextPostDialChar = 0; 365 if (Phone.DEBUG_PHONE) { 366 Rlog.d(LOG_TAG, "proceedAfterWildChar: new postDialString is " + 367 mPostDialString); 368 } 369 370 processNextPostDialChar(); 371 } 372 373 @Override 374 public void cancelPostDial() { 375 setPostDialState(PostDialState.CANCELLED); 376 } 377 378 /** 379 * Called when this Connection is being hung up locally (eg, user pressed "end") 380 */ 381 void 382 onHangupLocal() { 383 mCause = DisconnectCause.LOCAL; 384 } 385 386 /** Called when the connection has been disconnected */ 387 @Override 388 public boolean onDisconnect(int cause) { 389 Rlog.d(LOG_TAG, "onDisconnect: cause=" + cause); 390 if (mCause != DisconnectCause.LOCAL || cause == DisconnectCause.INCOMING_REJECTED) { 391 mCause = cause; 392 } 393 return onDisconnect(); 394 } 395 396 public boolean onDisconnect() { 397 boolean changed = false; 398 399 if (!mDisconnected) { 400 //mIndex = -1; 401 402 mDisconnectTime = System.currentTimeMillis(); 403 mDuration = SystemClock.elapsedRealtime() - mConnectTimeReal; 404 mDisconnected = true; 405 406 mOwner.mPhone.notifyDisconnect(this); 407 408 if (mParent != null) { 409 changed = mParent.connectionDisconnected(this); 410 } else { 411 Rlog.d(LOG_TAG, "onDisconnect: no parent"); 412 } 413 synchronized (this) { 414 if (mImsCall != null) mImsCall.close(); 415 mImsCall = null; 416 } 417 } 418 releaseWakeLock(); 419 return changed; 420 } 421 422 /** 423 * An incoming or outgoing call has connected 424 */ 425 void 426 onConnectedInOrOut() { 427 mConnectTime = System.currentTimeMillis(); 428 mConnectTimeReal = SystemClock.elapsedRealtime(); 429 mDuration = 0; 430 431 if (Phone.DEBUG_PHONE) { 432 Rlog.d(LOG_TAG, "onConnectedInOrOut: connectTime=" + mConnectTime); 433 } 434 435 if (!mIsIncoming) { 436 // outgoing calls only 437 processNextPostDialChar(); 438 } 439 releaseWakeLock(); 440 } 441 442 /*package*/ void 443 onStartedHolding() { 444 mHoldingStartTime = SystemClock.elapsedRealtime(); 445 } 446 /** 447 * Performs the appropriate action for a post-dial char, but does not 448 * notify application. returns false if the character is invalid and 449 * should be ignored 450 */ 451 private boolean 452 processPostDialChar(char c) { 453 if (PhoneNumberUtils.is12Key(c)) { 454 mOwner.sendDtmf(c, mHandler.obtainMessage(EVENT_DTMF_DONE)); 455 } else if (c == PhoneNumberUtils.PAUSE) { 456 // From TS 22.101: 457 // It continues... 458 // Upon the called party answering the UE shall send the DTMF digits 459 // automatically to the network after a delay of 3 seconds( 20 ). 460 // The digits shall be sent according to the procedures and timing 461 // specified in 3GPP TS 24.008 [13]. The first occurrence of the 462 // "DTMF Control Digits Separator" shall be used by the ME to 463 // distinguish between the addressing digits (i.e. the phone number) 464 // and the DTMF digits. Upon subsequent occurrences of the 465 // separator, 466 // the UE shall pause again for 3 seconds ( 20 ) before sending 467 // any further DTMF digits. 468 mHandler.sendMessageDelayed(mHandler.obtainMessage(EVENT_PAUSE_DONE), 469 PAUSE_DELAY_MILLIS); 470 } else if (c == PhoneNumberUtils.WAIT) { 471 setPostDialState(PostDialState.WAIT); 472 } else if (c == PhoneNumberUtils.WILD) { 473 setPostDialState(PostDialState.WILD); 474 } else { 475 return false; 476 } 477 478 return true; 479 } 480 481 @Override 482 protected void finalize() { 483 releaseWakeLock(); 484 } 485 486 private void 487 processNextPostDialChar() { 488 char c = 0; 489 Registrant postDialHandler; 490 491 if (mPostDialState == PostDialState.CANCELLED) { 492 //Rlog.d(LOG_TAG, "##### processNextPostDialChar: postDialState == CANCELLED, bail"); 493 return; 494 } 495 496 if (mPostDialString == null || mPostDialString.length() <= mNextPostDialChar) { 497 setPostDialState(PostDialState.COMPLETE); 498 499 // notifyMessage.arg1 is 0 on complete 500 c = 0; 501 } else { 502 boolean isValid; 503 504 setPostDialState(PostDialState.STARTED); 505 506 c = mPostDialString.charAt(mNextPostDialChar++); 507 508 isValid = processPostDialChar(c); 509 510 if (!isValid) { 511 // Will call processNextPostDialChar 512 mHandler.obtainMessage(EVENT_NEXT_POST_DIAL).sendToTarget(); 513 // Don't notify application 514 Rlog.e(LOG_TAG, "processNextPostDialChar: c=" + c + " isn't valid!"); 515 return; 516 } 517 } 518 519 notifyPostDialListenersNextChar(c); 520 521 // TODO: remove the following code since the handler no longer executes anything. 522 postDialHandler = mOwner.mPhone.getPostDialHandler(); 523 524 Message notifyMessage; 525 526 if (postDialHandler != null 527 && (notifyMessage = postDialHandler.messageForRegistrant()) != null) { 528 // The AsyncResult.result is the Connection object 529 PostDialState state = mPostDialState; 530 AsyncResult ar = AsyncResult.forMessage(notifyMessage); 531 ar.result = this; 532 ar.userObj = state; 533 534 // arg1 is the character that was/is being processed 535 notifyMessage.arg1 = c; 536 537 //Rlog.v(LOG_TAG, 538 // "##### processNextPostDialChar: send msg to postDialHandler, arg1=" + c); 539 notifyMessage.sendToTarget(); 540 } 541 } 542 543 /** 544 * Set post dial state and acquire wake lock while switching to "started" 545 * state, the wake lock will be released if state switches out of "started" 546 * state or after WAKE_LOCK_TIMEOUT_MILLIS. 547 * @param s new PostDialState 548 */ 549 private void setPostDialState(PostDialState s) { 550 if (mPostDialState != PostDialState.STARTED 551 && s == PostDialState.STARTED) { 552 acquireWakeLock(); 553 Message msg = mHandler.obtainMessage(EVENT_WAKE_LOCK_TIMEOUT); 554 mHandler.sendMessageDelayed(msg, WAKE_LOCK_TIMEOUT_MILLIS); 555 } else if (mPostDialState == PostDialState.STARTED 556 && s != PostDialState.STARTED) { 557 mHandler.removeMessages(EVENT_WAKE_LOCK_TIMEOUT); 558 releaseWakeLock(); 559 } 560 mPostDialState = s; 561 notifyPostDialListeners(); 562 } 563 564 private void 565 createWakeLock(Context context) { 566 PowerManager pm = (PowerManager) context.getSystemService(Context.POWER_SERVICE); 567 mPartialWakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, LOG_TAG); 568 } 569 570 private void 571 acquireWakeLock() { 572 Rlog.d(LOG_TAG, "acquireWakeLock"); 573 mPartialWakeLock.acquire(); 574 } 575 576 void 577 releaseWakeLock() { 578 if (mPartialWakeLock != null) { 579 synchronized (mPartialWakeLock) { 580 if (mPartialWakeLock.isHeld()) { 581 Rlog.d(LOG_TAG, "releaseWakeLock"); 582 mPartialWakeLock.release(); 583 } 584 } 585 } 586 } 587 588 private void fetchDtmfToneDelay(Phone phone) { 589 CarrierConfigManager configMgr = (CarrierConfigManager) 590 phone.getContext().getSystemService(Context.CARRIER_CONFIG_SERVICE); 591 PersistableBundle b = configMgr.getConfigForSubId(phone.getSubId()); 592 if (b != null) { 593 mDtmfToneDelay = b.getInt(CarrierConfigManager.KEY_IMS_DTMF_TONE_DELAY_INT); 594 } 595 } 596 597 @Override 598 public int getNumberPresentation() { 599 return mNumberPresentation; 600 } 601 602 @Override 603 public UUSInfo getUUSInfo() { 604 return mUusInfo; 605 } 606 607 @Override 608 public Connection getOrigConnection() { 609 return null; 610 } 611 612 @Override 613 public synchronized boolean isMultiparty() { 614 return mImsCall != null && mImsCall.isMultiparty(); 615 } 616 617 /** 618 * Where {@link #isMultiparty()} is {@code true}, determines if this {@link ImsCall} is the 619 * origin of the conference call (i.e. {@code #isConferenceHost()} is {@code true}), or if this 620 * {@link ImsCall} is a member of a conference hosted on another device. 621 * 622 * @return {@code true} if this call is the origin of the conference call it is a member of, 623 * {@code false} otherwise. 624 */ 625 @Override 626 public synchronized boolean isConferenceHost() { 627 return mImsCall != null && mImsCall.isConferenceHost(); 628 } 629 630 @Override 631 public boolean isMemberOfPeerConference() { 632 return !isConferenceHost(); 633 } 634 635 public synchronized ImsCall getImsCall() { 636 return mImsCall; 637 } 638 639 public synchronized void setImsCall(ImsCall imsCall) { 640 mImsCall = imsCall; 641 } 642 643 public void changeParent(ImsPhoneCall parent) { 644 mParent = parent; 645 } 646 647 /** 648 * @return {@code true} if the {@link ImsPhoneConnection} or its media capabilities have been 649 * changed, and {@code false} otherwise. 650 */ 651 public boolean update(ImsCall imsCall, ImsPhoneCall.State state) { 652 if (state == ImsPhoneCall.State.ACTIVE) { 653 // If the state of the call is active, but there is a pending request to the RIL to hold 654 // the call, we will skip this update. This is really a signalling delay or failure 655 // from the RIL, but we will prevent it from going through as we will end up erroneously 656 // making this call active when really it should be on hold. 657 if (imsCall.isPendingHold()) { 658 Rlog.w(LOG_TAG, "update : state is ACTIVE, but call is pending hold, skipping"); 659 return false; 660 } 661 662 if (mParent.getState().isRinging() || mParent.getState().isDialing()) { 663 onConnectedInOrOut(); 664 } 665 666 if (mParent.getState().isRinging() || mParent == mOwner.mBackgroundCall) { 667 //mForegroundCall should be IDLE 668 //when accepting WAITING call 669 //before accept WAITING call, 670 //the ACTIVE call should be held ahead 671 mParent.detach(this); 672 mParent = mOwner.mForegroundCall; 673 mParent.attach(this); 674 } 675 } else if (state == ImsPhoneCall.State.HOLDING) { 676 onStartedHolding(); 677 } 678 679 boolean updateParent = mParent.update(this, imsCall, state); 680 boolean updateAddressDisplay = updateAddressDisplay(imsCall); 681 boolean updateMediaCapabilities = updateMediaCapabilities(imsCall); 682 boolean updateExtras = updateExtras(imsCall); 683 684 return updateParent || updateAddressDisplay || updateMediaCapabilities || updateExtras; 685 } 686 687 @Override 688 public int getPreciseDisconnectCause() { 689 return mPreciseDisconnectCause; 690 } 691 692 public void setPreciseDisconnectCause(int cause) { 693 mPreciseDisconnectCause = cause; 694 } 695 696 /** 697 * Notifies this Connection of a request to disconnect a participant of the conference managed 698 * by the connection. 699 * 700 * @param endpoint the {@link android.net.Uri} of the participant to disconnect. 701 */ 702 @Override 703 public void onDisconnectConferenceParticipant(Uri endpoint) { 704 ImsCall imsCall = getImsCall(); 705 if (imsCall == null) { 706 return; 707 } 708 try { 709 imsCall.removeParticipants(new String[]{endpoint.toString()}); 710 } catch (ImsException e) { 711 // No session in place -- no change 712 Rlog.e(LOG_TAG, "onDisconnectConferenceParticipant: no session in place. "+ 713 "Failed to disconnect endpoint = " + endpoint); 714 } 715 } 716 717 /** 718 * Sets the conference connect time. Used when an {@code ImsConference} is created to out of 719 * this phone connection. 720 * 721 * @param conferenceConnectTime The conference connect time. 722 */ 723 public void setConferenceConnectTime(long conferenceConnectTime) { 724 mConferenceConnectTime = conferenceConnectTime; 725 } 726 727 /** 728 * @return The conference connect time. 729 */ 730 public long getConferenceConnectTime() { 731 return mConferenceConnectTime; 732 } 733 734 /** 735 * Check for a change in the address display related fields for the {@link ImsCall}, and 736 * update the {@link ImsPhoneConnection} with this information. 737 * 738 * @param imsCall The call to check for changes in address display fields. 739 * @return Whether the address display fields have been changed. 740 */ 741 public boolean updateAddressDisplay(ImsCall imsCall) { 742 if (imsCall == null) { 743 return false; 744 } 745 746 boolean changed = false; 747 ImsCallProfile callProfile = imsCall.getCallProfile(); 748 if (callProfile != null) { 749 String address = callProfile.getCallExtra(ImsCallProfile.EXTRA_OI); 750 String name = callProfile.getCallExtra(ImsCallProfile.EXTRA_CNA); 751 int nump = ImsCallProfile.OIRToPresentation( 752 callProfile.getCallExtraInt(ImsCallProfile.EXTRA_OIR)); 753 int namep = ImsCallProfile.OIRToPresentation( 754 callProfile.getCallExtraInt(ImsCallProfile.EXTRA_CNAP)); 755 if (Phone.DEBUG_PHONE) { 756 Rlog.d(LOG_TAG, "address = " + Rlog.pii(LOG_TAG, address) + " name = " + name + 757 " nump = " + nump + " namep = " + namep); 758 } 759 if(equalsHandlesNulls(mAddress, address)) { 760 mAddress = address; 761 changed = true; 762 } 763 if (TextUtils.isEmpty(name)) { 764 if (!TextUtils.isEmpty(mCnapName)) { 765 mCnapName = ""; 766 changed = true; 767 } 768 } else if (!name.equals(mCnapName)) { 769 mCnapName = name; 770 changed = true; 771 } 772 if (mNumberPresentation != nump) { 773 mNumberPresentation = nump; 774 changed = true; 775 } 776 if (mCnapNamePresentation != namep) { 777 mCnapNamePresentation = namep; 778 changed = true; 779 } 780 } 781 return changed; 782 } 783 784 /** 785 * Check for a change in the video capabilities and audio quality for the {@link ImsCall}, and 786 * update the {@link ImsPhoneConnection} with this information. 787 * 788 * @param imsCall The call to check for changes in media capabilities. 789 * @return Whether the media capabilities have been changed. 790 */ 791 public boolean updateMediaCapabilities(ImsCall imsCall) { 792 if (imsCall == null) { 793 return false; 794 } 795 796 boolean changed = false; 797 798 try { 799 // The actual call profile (negotiated between local and peer). 800 ImsCallProfile negotiatedCallProfile = imsCall.getCallProfile(); 801 802 if (negotiatedCallProfile != null) { 803 int oldVideoState = getVideoState(); 804 int newVideoState = ImsCallProfile 805 .getVideoStateFromImsCallProfile(negotiatedCallProfile); 806 807 if (oldVideoState != newVideoState) { 808 // The video state has changed. See also code in onReceiveSessionModifyResponse 809 // below. When the video enters a paused state, subsequent changes to the video 810 // state will not be reported by the modem. In onReceiveSessionModifyResponse 811 // we will be updating the current video state while paused to include any 812 // changes the modem reports via the video provider. When the video enters an 813 // unpaused state, we will resume passing the video states from the modem as is. 814 if (VideoProfile.isPaused(oldVideoState) && 815 !VideoProfile.isPaused(newVideoState)) { 816 // Video entered un-paused state; recognize updates from now on; we want to 817 // ensure that the new un-paused state is propagated to Telecom, so change 818 // this now. 819 mShouldIgnoreVideoStateChanges = false; 820 } 821 822 if (!mShouldIgnoreVideoStateChanges) { 823 if (mImsVideoCallProviderWrapper != null) { 824 mImsVideoCallProviderWrapper.onVideoStateChanged(newVideoState); 825 } 826 setVideoState(newVideoState); 827 changed = true; 828 } else { 829 Rlog.d(LOG_TAG, "updateMediaCapabilities - ignoring video state change " + 830 "due to paused state."); 831 } 832 833 if (!VideoProfile.isPaused(oldVideoState) && 834 VideoProfile.isPaused(newVideoState)) { 835 // Video entered pause state; ignore updates until un-paused. We do this 836 // after setVideoState is called above to ensure Telecom is notified that 837 // the device has entered paused state. 838 mShouldIgnoreVideoStateChanges = true; 839 } 840 } 841 } 842 843 // Check for a change in the capabilities for the call and update 844 // {@link ImsPhoneConnection} with this information. 845 int capabilities = getConnectionCapabilities(); 846 847 // Use carrier config to determine if downgrading directly to audio-only is supported. 848 if (mOwner.isCarrierDowngradeOfVtCallSupported()) { 849 capabilities = addCapability(capabilities, 850 Connection.Capability.SUPPORTS_DOWNGRADE_TO_VOICE_REMOTE | 851 Capability.SUPPORTS_DOWNGRADE_TO_VOICE_LOCAL); 852 } else { 853 capabilities = removeCapability(capabilities, 854 Connection.Capability.SUPPORTS_DOWNGRADE_TO_VOICE_REMOTE | 855 Capability.SUPPORTS_DOWNGRADE_TO_VOICE_LOCAL); 856 } 857 858 // Get the current local call capabilities which might be voice or video or both. 859 ImsCallProfile localCallProfile = imsCall.getLocalCallProfile(); 860 Rlog.v(LOG_TAG, "update localCallProfile=" + localCallProfile); 861 if (localCallProfile != null) { 862 capabilities = applyLocalCallCapabilities(localCallProfile, capabilities); 863 } 864 865 // Get the current remote call capabilities which might be voice or video or both. 866 ImsCallProfile remoteCallProfile = imsCall.getRemoteCallProfile(); 867 Rlog.v(LOG_TAG, "update remoteCallProfile=" + remoteCallProfile); 868 if (remoteCallProfile != null) { 869 capabilities = applyRemoteCallCapabilities(remoteCallProfile, capabilities); 870 } 871 if (getConnectionCapabilities() != capabilities) { 872 setConnectionCapabilities(capabilities); 873 changed = true; 874 } 875 876 int newAudioQuality = 877 getAudioQualityFromCallProfile(localCallProfile, remoteCallProfile); 878 if (getAudioQuality() != newAudioQuality) { 879 setAudioQuality(newAudioQuality); 880 changed = true; 881 } 882 } catch (ImsException e) { 883 // No session in place -- no change 884 } 885 886 return changed; 887 } 888 889 /** 890 * Updates the wifi state based on the {@link ImsCallProfile#EXTRA_CALL_RAT_TYPE}. 891 * The call is considered to be a WIFI call if the extra value is 892 * {@link ServiceState#RIL_RADIO_TECHNOLOGY_IWLAN}. 893 * 894 * @param extras The ImsCallProfile extras. 895 */ 896 private void updateWifiStateFromExtras(Bundle extras) { 897 if (extras.containsKey(ImsCallProfile.EXTRA_CALL_RAT_TYPE) || 898 extras.containsKey(ImsCallProfile.EXTRA_CALL_RAT_TYPE_ALT)) { 899 900 ImsCall call = getImsCall(); 901 boolean isWifi = false; 902 if (call != null) { 903 isWifi = call.isWifiCall(); 904 } 905 906 // Report any changes 907 if (isWifi() != isWifi) { 908 setWifi(isWifi); 909 } 910 } 911 } 912 913 /** 914 * Check for a change in call extras of {@link ImsCall}, and 915 * update the {@link ImsPhoneConnection} accordingly. 916 * 917 * @param imsCall The call to check for changes in extras. 918 * @return Whether the extras fields have been changed. 919 */ 920 boolean updateExtras(ImsCall imsCall) { 921 if (imsCall == null) { 922 return false; 923 } 924 925 final ImsCallProfile callProfile = imsCall.getCallProfile(); 926 final Bundle extras = callProfile != null ? callProfile.mCallExtras : null; 927 if (extras == null && DBG) { 928 Rlog.d(LOG_TAG, "Call profile extras are null."); 929 } 930 931 final boolean changed = !areBundlesEqual(extras, mExtras); 932 if (changed) { 933 updateWifiStateFromExtras(extras); 934 935 mExtras.clear(); 936 mExtras.putAll(extras); 937 setConnectionExtras(mExtras); 938 } 939 return changed; 940 } 941 942 private static boolean areBundlesEqual(Bundle extras, Bundle newExtras) { 943 if (extras == null || newExtras == null) { 944 return extras == newExtras; 945 } 946 947 if (extras.size() != newExtras.size()) { 948 return false; 949 } 950 951 for(String key : extras.keySet()) { 952 if (key != null) { 953 final Object value = extras.get(key); 954 final Object newValue = newExtras.get(key); 955 if (!Objects.equals(value, newValue)) { 956 return false; 957 } 958 } 959 } 960 return true; 961 } 962 963 /** 964 * Determines the {@link ImsPhoneConnection} audio quality based on the local and remote 965 * {@link ImsCallProfile}. Indicate a HD audio call if the local stream profile 966 * is AMR_WB, EVRC_WB, EVS_WB, EVS_SWB, EVS_FB and 967 * there is no remote restrict cause. 968 * 969 * @param localCallProfile The local call profile. 970 * @param remoteCallProfile The remote call profile. 971 * @return The audio quality. 972 */ 973 private int getAudioQualityFromCallProfile( 974 ImsCallProfile localCallProfile, ImsCallProfile remoteCallProfile) { 975 if (localCallProfile == null || remoteCallProfile == null 976 || localCallProfile.mMediaProfile == null) { 977 return AUDIO_QUALITY_STANDARD; 978 } 979 980 final boolean isEvsCodecHighDef = (localCallProfile.mMediaProfile.mAudioQuality 981 == ImsStreamMediaProfile.AUDIO_QUALITY_EVS_WB 982 || localCallProfile.mMediaProfile.mAudioQuality 983 == ImsStreamMediaProfile.AUDIO_QUALITY_EVS_SWB 984 || localCallProfile.mMediaProfile.mAudioQuality 985 == ImsStreamMediaProfile.AUDIO_QUALITY_EVS_FB); 986 987 final boolean isHighDef = (localCallProfile.mMediaProfile.mAudioQuality 988 == ImsStreamMediaProfile.AUDIO_QUALITY_AMR_WB 989 || localCallProfile.mMediaProfile.mAudioQuality 990 == ImsStreamMediaProfile.AUDIO_QUALITY_EVRC_WB 991 || isEvsCodecHighDef) 992 && remoteCallProfile.mRestrictCause == ImsCallProfile.CALL_RESTRICT_CAUSE_NONE; 993 return isHighDef ? AUDIO_QUALITY_HIGH_DEFINITION : AUDIO_QUALITY_STANDARD; 994 } 995 996 /** 997 * Provides a string representation of the {@link ImsPhoneConnection}. Primarily intended for 998 * use in log statements. 999 * 1000 * @return String representation of call. 1001 */ 1002 @Override 1003 public String toString() { 1004 StringBuilder sb = new StringBuilder(); 1005 sb.append("[ImsPhoneConnection objId: "); 1006 sb.append(System.identityHashCode(this)); 1007 sb.append(" telecomCallID: "); 1008 sb.append(getTelecomCallId()); 1009 sb.append(" address: "); 1010 sb.append(Rlog.pii(LOG_TAG, getAddress())); 1011 sb.append(" ImsCall: "); 1012 synchronized (this) { 1013 if (mImsCall == null) { 1014 sb.append("null"); 1015 } else { 1016 sb.append(mImsCall); 1017 } 1018 } 1019 sb.append("]"); 1020 return sb.toString(); 1021 } 1022 1023 @Override 1024 public void setVideoProvider(android.telecom.Connection.VideoProvider videoProvider) { 1025 super.setVideoProvider(videoProvider); 1026 1027 if (videoProvider instanceof ImsVideoCallProviderWrapper) { 1028 mImsVideoCallProviderWrapper = (ImsVideoCallProviderWrapper) videoProvider; 1029 } 1030 } 1031 1032 /** 1033 * Indicates whether current phone connection is emergency or not 1034 * @return boolean: true if emergency, false otherwise 1035 */ 1036 protected boolean isEmergency() { 1037 return mIsEmergency; 1038 } 1039 1040 /** 1041 * Handles notifications from the {@link ImsVideoCallProviderWrapper} of session modification 1042 * responses received. 1043 * 1044 * @param status The status of the original request. 1045 * @param requestProfile The requested video profile. 1046 * @param responseProfile The response upon video profile. 1047 */ 1048 @Override 1049 public void onReceiveSessionModifyResponse(int status, VideoProfile requestProfile, 1050 VideoProfile responseProfile) { 1051 if (status == android.telecom.Connection.VideoProvider.SESSION_MODIFY_REQUEST_SUCCESS && 1052 mShouldIgnoreVideoStateChanges) { 1053 int currentVideoState = getVideoState(); 1054 int newVideoState = responseProfile.getVideoState(); 1055 1056 // If the current video state is paused, the modem will not send us any changes to 1057 // the TX and RX bits of the video state. Until the video is un-paused we will 1058 // "fake out" the video state by applying the changes that the modem reports via a 1059 // response. 1060 1061 // First, find out whether there was a change to the TX or RX bits: 1062 int changedBits = currentVideoState ^ newVideoState; 1063 changedBits &= VideoProfile.STATE_BIDIRECTIONAL; 1064 if (changedBits == 0) { 1065 // No applicable change, bail out. 1066 return; 1067 } 1068 1069 // Turn off any existing bits that changed. 1070 currentVideoState &= ~(changedBits & currentVideoState); 1071 // Turn on any new bits that turned on. 1072 currentVideoState |= changedBits & newVideoState; 1073 1074 Rlog.d(LOG_TAG, "onReceiveSessionModifyResponse : received " + 1075 VideoProfile.videoStateToString(requestProfile.getVideoState()) + 1076 " / " + 1077 VideoProfile.videoStateToString(responseProfile.getVideoState()) + 1078 " while paused ; sending new videoState = " + 1079 VideoProfile.videoStateToString(currentVideoState)); 1080 setVideoState(currentVideoState); 1081 } 1082 } 1083 1084 /** 1085 * Issues a request to pause the video using {@link VideoProfile#STATE_PAUSED} from a source 1086 * other than the InCall UI. 1087 * 1088 * @param source The source of the pause request. 1089 */ 1090 public void pauseVideo(int source) { 1091 if (mImsVideoCallProviderWrapper == null) { 1092 return; 1093 } 1094 1095 mImsVideoCallProviderWrapper.pauseVideo(getVideoState(), source); 1096 } 1097 1098 /** 1099 * Issues a request to resume the video using {@link VideoProfile#STATE_PAUSED} from a source 1100 * other than the InCall UI. 1101 * 1102 * @param source The source of the resume request. 1103 */ 1104 public void resumeVideo(int source) { 1105 if (mImsVideoCallProviderWrapper == null) { 1106 return; 1107 } 1108 1109 mImsVideoCallProviderWrapper.resumeVideo(getVideoState(), source); 1110 } 1111 1112 /** 1113 * Determines if a specified source has issued a pause request. 1114 * 1115 * @param source The source. 1116 * @return {@code true} if the source issued a pause request, {@code false} otherwise. 1117 */ 1118 public boolean wasVideoPausedFromSource(int source) { 1119 if (mImsVideoCallProviderWrapper == null) { 1120 return false; 1121 } 1122 1123 return mImsVideoCallProviderWrapper.wasVideoPausedFromSource(source); 1124 } 1125} 1126