ImsPhoneConnection.java revision db5b6a50186bc3fc8d42c50f4d23c91b05412ec6
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.Log; 31import android.telecom.VideoProfile; 32import android.telephony.CarrierConfigManager; 33import android.telephony.DisconnectCause; 34import android.telephony.PhoneNumberUtils; 35import android.telephony.Rlog; 36import android.telephony.ServiceState; 37import android.text.TextUtils; 38 39import com.android.ims.ImsException; 40import com.android.ims.ImsStreamMediaProfile; 41import com.android.ims.internal.ImsVideoCallProviderWrapper; 42import com.android.internal.telephony.CallStateException; 43import com.android.internal.telephony.Connection; 44import com.android.internal.telephony.Phone; 45import com.android.internal.telephony.PhoneConstants; 46import com.android.internal.telephony.UUSInfo; 47 48import com.android.ims.ImsCall; 49import com.android.ims.ImsCallProfile; 50 51import java.util.Objects; 52 53/** 54 * {@hide} 55 */ 56public class ImsPhoneConnection extends Connection implements 57 ImsVideoCallProviderWrapper.ImsVideoProviderWrapperCallback { 58 59 private static final String LOG_TAG = "ImsPhoneConnection"; 60 private static final boolean DBG = true; 61 62 //***** Instance Variables 63 64 private ImsPhoneCallTracker mOwner; 65 private ImsPhoneCall mParent; 66 private ImsCall mImsCall; 67 private Bundle mExtras = new Bundle(); 68 69 private boolean mDisconnected; 70 71 /* 72 int mIndex; // index in ImsPhoneCallTracker.connections[], -1 if unassigned 73 // The GSM index is 1 + this 74 */ 75 76 /* 77 * These time/timespan values are based on System.currentTimeMillis(), 78 * i.e., "wall clock" time. 79 */ 80 private long mDisconnectTime; 81 82 private UUSInfo mUusInfo; 83 private Handler mHandler; 84 85 private PowerManager.WakeLock mPartialWakeLock; 86 87 // The cached connect time of the connection when it turns into a conference. 88 private long mConferenceConnectTime = 0; 89 90 // The cached delay to be used between DTMF tones fetched from carrier config. 91 private int mDtmfToneDelay = 0; 92 93 private boolean mIsEmergency = false; 94 95 /** 96 * Used to indicate that video state changes detected by 97 * {@link #updateMediaCapabilities(ImsCall)} should be ignored. When a video state change from 98 * unpaused to paused occurs, we set this flag and then update the existing video state when 99 * new {@link #onReceiveSessionModifyResponse(int, VideoProfile, VideoProfile)} callbacks come 100 * in. When the video un-pauses we continue receiving the video state updates. 101 */ 102 private boolean mShouldIgnoreVideoStateChanges = false; 103 104 /** 105 * Used to indicate whether the wifi state is based on 106 * {@link com.android.ims.ImsConnectionStateListener# 107 * onFeatureCapabilityChanged(int, int[], int[])} callbacks, or values received via the 108 * {@link ImsCallProfile#EXTRA_CALL_RAT_TYPE} extra. Util we receive a value via the extras, 109 * we will use the wifi state based on the {@code onFeatureCapabilityChanged}. Once a value 110 * is received via the extras, we will prefer those values going forward. 111 */ 112 private boolean mIsWifiStateFromExtras = false; 113 114 //***** Event Constants 115 private static final int EVENT_DTMF_DONE = 1; 116 private static final int EVENT_PAUSE_DONE = 2; 117 private static final int EVENT_NEXT_POST_DIAL = 3; 118 private static final int EVENT_WAKE_LOCK_TIMEOUT = 4; 119 private static final int EVENT_DTMF_DELAY_DONE = 5; 120 121 //***** Constants 122 private static final int PAUSE_DELAY_MILLIS = 3 * 1000; 123 private static final int WAKE_LOCK_TIMEOUT_MILLIS = 60*1000; 124 125 //***** Inner Classes 126 127 class MyHandler extends Handler { 128 MyHandler(Looper l) {super(l);} 129 130 @Override 131 public void 132 handleMessage(Message msg) { 133 134 switch (msg.what) { 135 case EVENT_NEXT_POST_DIAL: 136 case EVENT_DTMF_DELAY_DONE: 137 case EVENT_PAUSE_DONE: 138 processNextPostDialChar(); 139 break; 140 case EVENT_WAKE_LOCK_TIMEOUT: 141 releaseWakeLock(); 142 break; 143 case EVENT_DTMF_DONE: 144 // We may need to add a delay specified by carrier between DTMF tones that are 145 // sent out. 146 mHandler.sendMessageDelayed(mHandler.obtainMessage(EVENT_DTMF_DELAY_DONE), 147 mDtmfToneDelay); 148 break; 149 } 150 } 151 } 152 153 //***** Constructors 154 155 /** This is probably an MT call */ 156 public ImsPhoneConnection(Phone phone, ImsCall imsCall, ImsPhoneCallTracker ct, 157 ImsPhoneCall parent, boolean isUnknown) { 158 super(PhoneConstants.PHONE_TYPE_IMS); 159 createWakeLock(phone.getContext()); 160 acquireWakeLock(); 161 162 mOwner = ct; 163 mHandler = new MyHandler(mOwner.getLooper()); 164 mImsCall = imsCall; 165 166 if ((imsCall != null) && (imsCall.getCallProfile() != null)) { 167 mAddress = imsCall.getCallProfile().getCallExtra(ImsCallProfile.EXTRA_OI); 168 mCnapName = imsCall.getCallProfile().getCallExtra(ImsCallProfile.EXTRA_CNA); 169 mNumberPresentation = ImsCallProfile.OIRToPresentation( 170 imsCall.getCallProfile().getCallExtraInt(ImsCallProfile.EXTRA_OIR)); 171 mCnapNamePresentation = ImsCallProfile.OIRToPresentation( 172 imsCall.getCallProfile().getCallExtraInt(ImsCallProfile.EXTRA_CNAP)); 173 updateMediaCapabilities(imsCall); 174 } else { 175 mNumberPresentation = PhoneConstants.PRESENTATION_UNKNOWN; 176 mCnapNamePresentation = PhoneConstants.PRESENTATION_UNKNOWN; 177 } 178 179 mIsIncoming = !isUnknown; 180 mCreateTime = System.currentTimeMillis(); 181 mUusInfo = null; 182 183 updateWifiState(); 184 185 // Ensure any extras set on the ImsCallProfile at the start of the call are cached locally 186 // in the ImsPhoneConnection. This isn't going to inform any listeners (since the original 187 // connection is not likely to be associated with a TelephonyConnection yet). 188 updateExtras(imsCall); 189 190 mParent = parent; 191 mParent.attach(this, 192 (mIsIncoming? ImsPhoneCall.State.INCOMING: ImsPhoneCall.State.DIALING)); 193 194 fetchDtmfToneDelay(phone); 195 } 196 197 /** This is an MO call, created when dialing */ 198 public ImsPhoneConnection(Phone phone, String dialString, ImsPhoneCallTracker ct, 199 ImsPhoneCall parent, boolean isEmergency) { 200 super(PhoneConstants.PHONE_TYPE_IMS); 201 createWakeLock(phone.getContext()); 202 acquireWakeLock(); 203 204 mOwner = ct; 205 mHandler = new MyHandler(mOwner.getLooper()); 206 207 mDialString = dialString; 208 209 mAddress = PhoneNumberUtils.extractNetworkPortionAlt(dialString); 210 mPostDialString = PhoneNumberUtils.extractPostDialPortion(dialString); 211 212 //mIndex = -1; 213 214 mIsIncoming = false; 215 mCnapName = null; 216 mCnapNamePresentation = PhoneConstants.PRESENTATION_ALLOWED; 217 mNumberPresentation = PhoneConstants.PRESENTATION_ALLOWED; 218 mCreateTime = System.currentTimeMillis(); 219 220 mParent = parent; 221 parent.attachFake(this, ImsPhoneCall.State.DIALING); 222 223 mIsEmergency = isEmergency; 224 225 fetchDtmfToneDelay(phone); 226 } 227 228 public void dispose() { 229 } 230 231 static boolean 232 equalsHandlesNulls (Object a, Object b) { 233 return (a == null) ? (b == null) : a.equals (b); 234 } 235 236 private static int applyLocalCallCapabilities(ImsCallProfile localProfile, int capabilities) { 237 capabilities = removeCapability(capabilities, 238 Connection.Capability.SUPPORTS_VT_LOCAL_BIDIRECTIONAL 239 | Connection.Capability.SUPPORTS_DOWNGRADE_TO_VOICE_LOCAL); 240 241 switch (localProfile.mCallType) { 242 case ImsCallProfile.CALL_TYPE_VT: 243 capabilities = addCapability(capabilities, 244 Connection.Capability.SUPPORTS_VT_LOCAL_BIDIRECTIONAL); 245 break; 246 case ImsCallProfile.CALL_TYPE_VIDEO_N_VOICE: 247 capabilities = addCapability(capabilities, 248 Connection.Capability.SUPPORTS_VT_LOCAL_BIDIRECTIONAL 249 | Connection.Capability.SUPPORTS_DOWNGRADE_TO_VOICE_LOCAL); 250 break; 251 } 252 return capabilities; 253 } 254 255 private static int applyRemoteCallCapabilities(ImsCallProfile remoteProfile, int capabilities) { 256 capabilities = removeCapability(capabilities, 257 Connection.Capability.SUPPORTS_VT_REMOTE_BIDIRECTIONAL 258 | Connection.Capability.SUPPORTS_DOWNGRADE_TO_VOICE_REMOTE); 259 260 switch (remoteProfile.mCallType) { 261 case ImsCallProfile.CALL_TYPE_VT: 262 capabilities = addCapability(capabilities, 263 Connection.Capability.SUPPORTS_VT_REMOTE_BIDIRECTIONAL); 264 break; 265 case ImsCallProfile.CALL_TYPE_VIDEO_N_VOICE: 266 capabilities = addCapability(capabilities, 267 Connection.Capability.SUPPORTS_VT_REMOTE_BIDIRECTIONAL 268 | Connection.Capability.SUPPORTS_DOWNGRADE_TO_VOICE_REMOTE); 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) mCause = cause; 396 return onDisconnect(); 397 } 398 399 public boolean onDisconnect() { 400 boolean changed = false; 401 402 if (!mDisconnected) { 403 //mIndex = -1; 404 405 mDisconnectTime = System.currentTimeMillis(); 406 mDuration = SystemClock.elapsedRealtime() - mConnectTimeReal; 407 mDisconnected = true; 408 409 mOwner.mPhone.notifyDisconnect(this); 410 411 if (mParent != null) { 412 changed = mParent.connectionDisconnected(this); 413 } else { 414 Rlog.d(LOG_TAG, "onDisconnect: no parent"); 415 } 416 if (mImsCall != null) mImsCall.close(); 417 mImsCall = null; 418 } 419 releaseWakeLock(); 420 return changed; 421 } 422 423 /** 424 * An incoming or outgoing call has connected 425 */ 426 void 427 onConnectedInOrOut() { 428 mConnectTime = System.currentTimeMillis(); 429 mConnectTimeReal = SystemClock.elapsedRealtime(); 430 mDuration = 0; 431 432 if (Phone.DEBUG_PHONE) { 433 Rlog.d(LOG_TAG, "onConnectedInOrOut: connectTime=" + mConnectTime); 434 } 435 436 if (!mIsIncoming) { 437 // outgoing calls only 438 processNextPostDialChar(); 439 } 440 releaseWakeLock(); 441 } 442 443 /*package*/ void 444 onStartedHolding() { 445 mHoldingStartTime = SystemClock.elapsedRealtime(); 446 } 447 /** 448 * Performs the appropriate action for a post-dial char, but does not 449 * notify application. returns false if the character is invalid and 450 * should be ignored 451 */ 452 private boolean 453 processPostDialChar(char c) { 454 if (PhoneNumberUtils.is12Key(c)) { 455 mOwner.sendDtmf(c, mHandler.obtainMessage(EVENT_DTMF_DONE)); 456 } else if (c == PhoneNumberUtils.PAUSE) { 457 // From TS 22.101: 458 // It continues... 459 // Upon the called party answering the UE shall send the DTMF digits 460 // automatically to the network after a delay of 3 seconds( 20 ). 461 // The digits shall be sent according to the procedures and timing 462 // specified in 3GPP TS 24.008 [13]. The first occurrence of the 463 // "DTMF Control Digits Separator" shall be used by the ME to 464 // distinguish between the addressing digits (i.e. the phone number) 465 // and the DTMF digits. Upon subsequent occurrences of the 466 // separator, 467 // the UE shall pause again for 3 seconds ( 20 ) before sending 468 // any further DTMF digits. 469 mHandler.sendMessageDelayed(mHandler.obtainMessage(EVENT_PAUSE_DONE), 470 PAUSE_DELAY_MILLIS); 471 } else if (c == PhoneNumberUtils.WAIT) { 472 setPostDialState(PostDialState.WAIT); 473 } else if (c == PhoneNumberUtils.WILD) { 474 setPostDialState(PostDialState.WILD); 475 } else { 476 return false; 477 } 478 479 return true; 480 } 481 482 @Override 483 protected void finalize() { 484 releaseWakeLock(); 485 } 486 487 private void 488 processNextPostDialChar() { 489 char c = 0; 490 Registrant postDialHandler; 491 492 if (mPostDialState == PostDialState.CANCELLED) { 493 //Rlog.d(LOG_TAG, "##### processNextPostDialChar: postDialState == CANCELLED, bail"); 494 return; 495 } 496 497 if (mPostDialString == null || mPostDialString.length() <= mNextPostDialChar) { 498 setPostDialState(PostDialState.COMPLETE); 499 500 // notifyMessage.arg1 is 0 on complete 501 c = 0; 502 } else { 503 boolean isValid; 504 505 setPostDialState(PostDialState.STARTED); 506 507 c = mPostDialString.charAt(mNextPostDialChar++); 508 509 isValid = processPostDialChar(c); 510 511 if (!isValid) { 512 // Will call processNextPostDialChar 513 mHandler.obtainMessage(EVENT_NEXT_POST_DIAL).sendToTarget(); 514 // Don't notify application 515 Rlog.e(LOG_TAG, "processNextPostDialChar: c=" + c + " isn't valid!"); 516 return; 517 } 518 } 519 520 notifyPostDialListenersNextChar(c); 521 522 // TODO: remove the following code since the handler no longer executes anything. 523 postDialHandler = mOwner.mPhone.getPostDialHandler(); 524 525 Message notifyMessage; 526 527 if (postDialHandler != null 528 && (notifyMessage = postDialHandler.messageForRegistrant()) != null) { 529 // The AsyncResult.result is the Connection object 530 PostDialState state = mPostDialState; 531 AsyncResult ar = AsyncResult.forMessage(notifyMessage); 532 ar.result = this; 533 ar.userObj = state; 534 535 // arg1 is the character that was/is being processed 536 notifyMessage.arg1 = c; 537 538 //Rlog.v(LOG_TAG, 539 // "##### processNextPostDialChar: send msg to postDialHandler, arg1=" + c); 540 notifyMessage.sendToTarget(); 541 } 542 } 543 544 /** 545 * Set post dial state and acquire wake lock while switching to "started" 546 * state, the wake lock will be released if state switches out of "started" 547 * state or after WAKE_LOCK_TIMEOUT_MILLIS. 548 * @param s new PostDialState 549 */ 550 private void setPostDialState(PostDialState s) { 551 if (mPostDialState != PostDialState.STARTED 552 && s == PostDialState.STARTED) { 553 acquireWakeLock(); 554 Message msg = mHandler.obtainMessage(EVENT_WAKE_LOCK_TIMEOUT); 555 mHandler.sendMessageDelayed(msg, WAKE_LOCK_TIMEOUT_MILLIS); 556 } else if (mPostDialState == PostDialState.STARTED 557 && s != PostDialState.STARTED) { 558 mHandler.removeMessages(EVENT_WAKE_LOCK_TIMEOUT); 559 releaseWakeLock(); 560 } 561 mPostDialState = s; 562 notifyPostDialListeners(); 563 } 564 565 private void 566 createWakeLock(Context context) { 567 PowerManager pm = (PowerManager) context.getSystemService(Context.POWER_SERVICE); 568 mPartialWakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, LOG_TAG); 569 } 570 571 private void 572 acquireWakeLock() { 573 Rlog.d(LOG_TAG, "acquireWakeLock"); 574 mPartialWakeLock.acquire(); 575 } 576 577 void 578 releaseWakeLock() { 579 synchronized(mPartialWakeLock) { 580 if (mPartialWakeLock.isHeld()) { 581 Rlog.d(LOG_TAG, "releaseWakeLock"); 582 mPartialWakeLock.release(); 583 } 584 } 585 } 586 587 private void fetchDtmfToneDelay(Phone phone) { 588 CarrierConfigManager configMgr = (CarrierConfigManager) 589 phone.getContext().getSystemService(Context.CARRIER_CONFIG_SERVICE); 590 PersistableBundle b = configMgr.getConfigForSubId(phone.getSubId()); 591 if (b != null) { 592 mDtmfToneDelay = b.getInt(CarrierConfigManager.KEY_IMS_DTMF_TONE_DELAY_INT); 593 } 594 } 595 596 @Override 597 public int getNumberPresentation() { 598 return mNumberPresentation; 599 } 600 601 @Override 602 public UUSInfo getUUSInfo() { 603 return mUusInfo; 604 } 605 606 @Override 607 public Connection getOrigConnection() { 608 return null; 609 } 610 611 @Override 612 public boolean isMultiparty() { 613 return mImsCall != null && mImsCall.isMultiparty(); 614 } 615 616 /** 617 * Where {@link #isMultiparty()} is {@code true}, determines if this {@link ImsCall} is the 618 * origin of the conference call (i.e. {@code #isConferenceHost()} is {@code true}), or if this 619 * {@link ImsCall} is a member of a conference hosted on another device. 620 * 621 * @return {@code true} if this call is the origin of the conference call it is a member of, 622 * {@code false} otherwise. 623 */ 624 @Override 625 public boolean isConferenceHost() { 626 if (mImsCall == null) { 627 return false; 628 } 629 return mImsCall.isConferenceHost(); 630 } 631 632 @Override 633 public boolean isMemberOfPeerConference() { 634 return !isConferenceHost(); 635 } 636 637 public ImsCall getImsCall() { 638 return mImsCall; 639 } 640 641 public void setImsCall(ImsCall imsCall) { 642 mImsCall = imsCall; 643 } 644 645 public void changeParent(ImsPhoneCall parent) { 646 mParent = parent; 647 } 648 649 /** 650 * @return {@code true} if the {@link ImsPhoneConnection} or its media capabilities have been 651 * changed, and {@code false} otherwise. 652 */ 653 public boolean update(ImsCall imsCall, ImsPhoneCall.State state) { 654 if (state == ImsPhoneCall.State.ACTIVE) { 655 // If the state of the call is active, but there is a pending request to the RIL to hold 656 // the call, we will skip this update. This is really a signalling delay or failure 657 // from the RIL, but we will prevent it from going through as we will end up erroneously 658 // making this call active when really it should be on hold. 659 if (imsCall.isPendingHold()) { 660 Rlog.w(LOG_TAG, "update : state is ACTIVE, but call is pending hold, skipping"); 661 return false; 662 } 663 664 if (mParent.getState().isRinging() || mParent.getState().isDialing()) { 665 onConnectedInOrOut(); 666 } 667 668 if (mParent.getState().isRinging() || mParent == mOwner.mBackgroundCall) { 669 //mForegroundCall should be IDLE 670 //when accepting WAITING call 671 //before accept WAITING call, 672 //the ACTIVE call should be held ahead 673 mParent.detach(this); 674 mParent = mOwner.mForegroundCall; 675 mParent.attach(this); 676 } 677 } else if (state == ImsPhoneCall.State.HOLDING) { 678 onStartedHolding(); 679 } 680 681 boolean updateParent = mParent.update(this, imsCall, state); 682 boolean updateWifiState = updateWifiState(); 683 boolean updateAddressDisplay = updateAddressDisplay(imsCall); 684 boolean updateMediaCapabilities = updateMediaCapabilities(imsCall); 685 boolean updateExtras = updateExtras(imsCall); 686 687 return updateParent || updateWifiState || updateAddressDisplay || updateMediaCapabilities 688 || updateExtras; 689 } 690 691 @Override 692 public int getPreciseDisconnectCause() { 693 return 0; 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 = " + 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 setVideoState(newVideoState); 824 changed = true; 825 } else { 826 Rlog.d(LOG_TAG, "updateMediaCapabilities - ignoring video state change " + 827 "due to paused state."); 828 } 829 830 if (!VideoProfile.isPaused(oldVideoState) && 831 VideoProfile.isPaused(newVideoState)) { 832 // Video entered pause state; ignore updates until un-paused. We do this 833 // after setVideoState is called above to ensure Telecom is notified that 834 // the device has entered paused state. 835 mShouldIgnoreVideoStateChanges = true; 836 } 837 } 838 } 839 840 // Check for a change in the capabilities for the call and update 841 // {@link ImsPhoneConnection} with this information. 842 int capabilities = getConnectionCapabilities(); 843 // Get the current local call capabilities which might be voice or video or both. 844 ImsCallProfile localCallProfile = imsCall.getLocalCallProfile(); 845 Rlog.v(LOG_TAG, "update localCallProfile=" + localCallProfile); 846 if (localCallProfile != null) { 847 capabilities = applyLocalCallCapabilities(localCallProfile, capabilities); 848 } 849 850 // Get the current remote call capabilities which might be voice or video or both. 851 ImsCallProfile remoteCallProfile = imsCall.getRemoteCallProfile(); 852 Rlog.v(LOG_TAG, "update remoteCallProfile=" + remoteCallProfile); 853 if (remoteCallProfile != null) { 854 capabilities = applyRemoteCallCapabilities(remoteCallProfile, capabilities); 855 } 856 if (getConnectionCapabilities() != capabilities) { 857 setConnectionCapabilities(capabilities); 858 changed = true; 859 } 860 861 int newAudioQuality = 862 getAudioQualityFromCallProfile(localCallProfile, remoteCallProfile); 863 if (getAudioQuality() != newAudioQuality) { 864 setAudioQuality(newAudioQuality); 865 changed = true; 866 } 867 } catch (ImsException e) { 868 // No session in place -- no change 869 } 870 871 return changed; 872 } 873 874 /** 875 * Check for a change in the wifi state of the ImsPhoneCallTracker and update the 876 * {@link ImsPhoneConnection} with this information. 877 * 878 * @return Whether the ImsPhoneCallTracker's usage of wifi has been changed. 879 */ 880 public boolean updateWifiState() { 881 // If we've received the wifi state via the ImsCallProfile.EXTRA_CALL_RAT_TYPE extra, we 882 // will no longer use state updates which are based on the onFeatureCapabilityChanged 883 // callback. 884 if (mIsWifiStateFromExtras) { 885 return false; 886 } 887 888 Rlog.d(LOG_TAG, "updateWifiState: " + mOwner.isVowifiEnabled()); 889 if (isWifi() != mOwner.isVowifiEnabled()) { 890 setWifi(mOwner.isVowifiEnabled()); 891 return true; 892 } 893 return false; 894 } 895 896 /** 897 * Updates the wifi state based on the {@link ImsCallProfile#EXTRA_CALL_RAT_TYPE}. 898 * The call is considered to be a WIFI call if the extra value is 899 * {@link ServiceState#RIL_RADIO_TECHNOLOGY_IWLAN}. 900 * 901 * @param extras The ImsCallProfile extras. 902 */ 903 private void updateWifiStateFromExtras(Bundle extras) { 904 if (extras.containsKey(ImsCallProfile.EXTRA_CALL_RAT_TYPE)) { 905 // The RIL (sadly) sends us the EXTRA_CALL_RAT_TYPE as a string extra, rather than an 906 // integer extra, so we need to parse it. 907 int radioTechnology; 908 try { 909 radioTechnology = Integer.parseInt(extras.getString( 910 ImsCallProfile.EXTRA_CALL_RAT_TYPE)); 911 } catch (NumberFormatException nfe) { 912 radioTechnology = ServiceState.RIL_RADIO_TECHNOLOGY_UNKNOWN; 913 } 914 915 // We've received the extra indicating the radio technology, so we will continue to 916 // prefer the radio technology received via this extra going forward. 917 mIsWifiStateFromExtras = true; 918 919 boolean isWifi = radioTechnology == ServiceState.RIL_RADIO_TECHNOLOGY_IWLAN; 920 921 // Report any changes 922 if (isWifi() != isWifi) { 923 setWifi(isWifi); 924 } 925 } 926 } 927 928 /** 929 * Check for a change in call extras of {@link ImsCall}, and 930 * update the {@link ImsPhoneConnection} accordingly. 931 * 932 * @param imsCall The call to check for changes in extras. 933 * @return Whether the extras fields have been changed. 934 */ 935 boolean updateExtras(ImsCall imsCall) { 936 if (imsCall == null) { 937 return false; 938 } 939 940 final ImsCallProfile callProfile = imsCall.getCallProfile(); 941 final Bundle extras = callProfile != null ? callProfile.mCallExtras : null; 942 if (extras == null && DBG) { 943 Rlog.d(LOG_TAG, "Call profile extras are null."); 944 } 945 946 final boolean changed = !areBundlesEqual(extras, mExtras); 947 if (changed) { 948 updateWifiStateFromExtras(extras); 949 950 mExtras.clear(); 951 mExtras.putAll(extras); 952 setConnectionExtras(mExtras); 953 } 954 return changed; 955 } 956 957 private static boolean areBundlesEqual(Bundle extras, Bundle newExtras) { 958 if (extras == null || newExtras == null) { 959 return extras == newExtras; 960 } 961 962 if (extras.size() != newExtras.size()) { 963 return false; 964 } 965 966 for(String key : extras.keySet()) { 967 if (key != null) { 968 final Object value = extras.get(key); 969 final Object newValue = newExtras.get(key); 970 if (!Objects.equals(value, newValue)) { 971 return false; 972 } 973 } 974 } 975 return true; 976 } 977 978 /** 979 * Determines the {@link ImsPhoneConnection} audio quality based on the local and remote 980 * {@link ImsCallProfile}. Indicate a HD audio call if the local stream profile 981 * is AMR_WB, EVRC_WB, EVS_WB, EVS_SWB, EVS_FB and 982 * there is no remote restrict cause. 983 * 984 * @param localCallProfile The local call profile. 985 * @param remoteCallProfile The remote call profile. 986 * @return The audio quality. 987 */ 988 private int getAudioQualityFromCallProfile( 989 ImsCallProfile localCallProfile, ImsCallProfile remoteCallProfile) { 990 if (localCallProfile == null || remoteCallProfile == null 991 || localCallProfile.mMediaProfile == null) { 992 return AUDIO_QUALITY_STANDARD; 993 } 994 995 final boolean isEvsCodecHighDef = (localCallProfile.mMediaProfile.mAudioQuality 996 == ImsStreamMediaProfile.AUDIO_QUALITY_EVS_WB 997 || localCallProfile.mMediaProfile.mAudioQuality 998 == ImsStreamMediaProfile.AUDIO_QUALITY_EVS_SWB 999 || localCallProfile.mMediaProfile.mAudioQuality 1000 == ImsStreamMediaProfile.AUDIO_QUALITY_EVS_FB); 1001 1002 final boolean isHighDef = (localCallProfile.mMediaProfile.mAudioQuality 1003 == ImsStreamMediaProfile.AUDIO_QUALITY_AMR_WB 1004 || localCallProfile.mMediaProfile.mAudioQuality 1005 == ImsStreamMediaProfile.AUDIO_QUALITY_EVRC_WB 1006 || isEvsCodecHighDef) 1007 && remoteCallProfile.mRestrictCause == ImsCallProfile.CALL_RESTRICT_CAUSE_NONE; 1008 return isHighDef ? AUDIO_QUALITY_HIGH_DEFINITION : AUDIO_QUALITY_STANDARD; 1009 } 1010 1011 /** 1012 * Provides a string representation of the {@link ImsPhoneConnection}. Primarily intended for 1013 * use in log statements. 1014 * 1015 * @return String representation of call. 1016 */ 1017 @Override 1018 public String toString() { 1019 StringBuilder sb = new StringBuilder(); 1020 sb.append("[ImsPhoneConnection objId: "); 1021 sb.append(System.identityHashCode(this)); 1022 sb.append(" telecomCallID: "); 1023 sb.append(getTelecomCallId()); 1024 sb.append(" address: "); 1025 sb.append(Log.pii(getAddress())); 1026 sb.append(" ImsCall: "); 1027 if (mImsCall == null) { 1028 sb.append("null"); 1029 } else { 1030 sb.append(mImsCall); 1031 } 1032 sb.append("]"); 1033 return sb.toString(); 1034 } 1035 1036 /** 1037 * Indicates whether current phone connection is emergency or not 1038 * @return boolean: true if emergency, false otherwise 1039 */ 1040 protected boolean isEmergency() { 1041 return mIsEmergency; 1042 } 1043 1044 /** 1045 * Handles notifications from the {@link ImsVideoCallProviderWrapper} of session modification 1046 * responses received. 1047 * 1048 * @param status The status of the original request. 1049 * @param requestProfile The requested video profile. 1050 * @param responseProfile The response upon video profile. 1051 */ 1052 @Override 1053 public void onReceiveSessionModifyResponse(int status, VideoProfile requestProfile, 1054 VideoProfile responseProfile) { 1055 if (status == android.telecom.Connection.VideoProvider.SESSION_MODIFY_REQUEST_SUCCESS && 1056 mShouldIgnoreVideoStateChanges) { 1057 int currentVideoState = getVideoState(); 1058 int newVideoState = responseProfile.getVideoState(); 1059 1060 // If the current video state is paused, the modem will not send us any changes to 1061 // the TX and RX bits of the video state. Until the video is un-paused we will 1062 // "fake out" the video state by applying the changes that the modem reports via a 1063 // response. 1064 1065 // First, find out whether there was a change to the TX or RX bits: 1066 int changedBits = currentVideoState ^ newVideoState; 1067 changedBits &= VideoProfile.STATE_BIDIRECTIONAL; 1068 if (changedBits == 0) { 1069 // No applicable change, bail out. 1070 return; 1071 } 1072 1073 // Turn off any existing bits that changed. 1074 currentVideoState &= ~(changedBits & currentVideoState); 1075 // Turn on any new bits that turned on. 1076 currentVideoState |= changedBits & newVideoState; 1077 1078 Rlog.d(LOG_TAG, "onReceiveSessionModifyResponse : received " + 1079 VideoProfile.videoStateToString(requestProfile.getVideoState()) + 1080 " / " + 1081 VideoProfile.videoStateToString(responseProfile.getVideoState()) + 1082 " while paused ; sending new videoState = " + 1083 VideoProfile.videoStateToString(currentVideoState)); 1084 setVideoState(currentVideoState); 1085 } 1086 } 1087} 1088