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