ImsPhoneConnection.java revision 6bb557b243f3fc9984b82319026519608ada2c9c
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.telephony.CarrierConfigManager; 32import android.telephony.DisconnectCause; 33import android.telephony.PhoneNumberUtils; 34import android.telephony.Rlog; 35import android.text.TextUtils; 36 37import com.android.ims.ImsException; 38import com.android.ims.ImsStreamMediaProfile; 39import com.android.internal.telephony.CallStateException; 40import com.android.internal.telephony.Connection; 41import com.android.internal.telephony.Phone; 42import com.android.internal.telephony.PhoneConstants; 43import com.android.internal.telephony.UUSInfo; 44 45import com.android.ims.ImsCall; 46import com.android.ims.ImsCallProfile; 47 48/** 49 * {@hide} 50 */ 51public class ImsPhoneConnection extends Connection { 52 private static final String LOG_TAG = "ImsPhoneConnection"; 53 private static final boolean DBG = true; 54 55 //***** Instance Variables 56 57 private ImsPhoneCallTracker mOwner; 58 private ImsPhoneCall mParent; 59 private ImsCall mImsCall; 60 61 private String mPostDialString; // outgoing calls only 62 private boolean mDisconnected; 63 64 /* 65 int mIndex; // index in ImsPhoneCallTracker.connections[], -1 if unassigned 66 // The GSM index is 1 + this 67 */ 68 69 /* 70 * These time/timespan values are based on System.currentTimeMillis(), 71 * i.e., "wall clock" time. 72 */ 73 private long mDisconnectTime; 74 75 private int mNextPostDialChar; // index into postDialString 76 77 private int mCause = DisconnectCause.NOT_DISCONNECTED; 78 private PostDialState mPostDialState = PostDialState.NOT_STARTED; 79 private UUSInfo mUusInfo; 80 private Handler mHandler; 81 82 private PowerManager.WakeLock mPartialWakeLock; 83 84 // The cached connect time of the connection when it turns into a conference. 85 private long mConferenceConnectTime = 0; 86 87 // The cached delay to be used between DTMF tones fetched from carrier config. 88 private int mDtmfToneDelay = 0; 89 90 //***** Event Constants 91 private static final int EVENT_DTMF_DONE = 1; 92 private static final int EVENT_PAUSE_DONE = 2; 93 private static final int EVENT_NEXT_POST_DIAL = 3; 94 private static final int EVENT_WAKE_LOCK_TIMEOUT = 4; 95 private static final int EVENT_DTMF_DELAY_DONE = 5; 96 97 //***** Constants 98 private static final int PAUSE_DELAY_MILLIS = 3 * 1000; 99 private static final int WAKE_LOCK_TIMEOUT_MILLIS = 60*1000; 100 101 //***** Inner Classes 102 103 class MyHandler extends Handler { 104 MyHandler(Looper l) {super(l);} 105 106 @Override 107 public void 108 handleMessage(Message msg) { 109 110 switch (msg.what) { 111 case EVENT_NEXT_POST_DIAL: 112 case EVENT_DTMF_DELAY_DONE: 113 case EVENT_PAUSE_DONE: 114 processNextPostDialChar(); 115 break; 116 case EVENT_WAKE_LOCK_TIMEOUT: 117 releaseWakeLock(); 118 break; 119 case EVENT_DTMF_DONE: 120 // We may need to add a delay specified by carrier between DTMF tones that are 121 // sent out. 122 mHandler.sendMessageDelayed(mHandler.obtainMessage(EVENT_DTMF_DELAY_DONE), 123 mDtmfToneDelay); 124 break; 125 } 126 } 127 } 128 129 //***** Constructors 130 131 /** This is probably an MT call */ 132 /*package*/ 133 ImsPhoneConnection(ImsPhone phone, ImsCall imsCall, ImsPhoneCallTracker ct, 134 ImsPhoneCall parent, boolean isUnknown) { 135 createWakeLock(phone.getContext()); 136 acquireWakeLock(); 137 138 mOwner = ct; 139 mHandler = new MyHandler(mOwner.getLooper()); 140 mImsCall = imsCall; 141 142 if ((imsCall != null) && (imsCall.getCallProfile() != null)) { 143 mAddress = imsCall.getCallProfile().getCallExtra(ImsCallProfile.EXTRA_OI); 144 mCnapName = imsCall.getCallProfile().getCallExtra(ImsCallProfile.EXTRA_CNA); 145 mNumberPresentation = ImsCallProfile.OIRToPresentation( 146 imsCall.getCallProfile().getCallExtraInt(ImsCallProfile.EXTRA_OIR)); 147 mCnapNamePresentation = ImsCallProfile.OIRToPresentation( 148 imsCall.getCallProfile().getCallExtraInt(ImsCallProfile.EXTRA_CNAP)); 149 updateMediaCapabilities(imsCall); 150 } else { 151 mNumberPresentation = PhoneConstants.PRESENTATION_UNKNOWN; 152 mCnapNamePresentation = PhoneConstants.PRESENTATION_UNKNOWN; 153 } 154 155 mIsIncoming = !isUnknown; 156 mCreateTime = System.currentTimeMillis(); 157 mUusInfo = null; 158 159 //mIndex = index; 160 161 updateWifiState(); 162 163 mParent = parent; 164 mParent.attach(this, 165 (mIsIncoming? ImsPhoneCall.State.INCOMING: ImsPhoneCall.State.DIALING)); 166 167 fetchDtmfToneDelay(phone); 168 } 169 170 /** This is an MO call, created when dialing */ 171 /*package*/ 172 ImsPhoneConnection(ImsPhone phone, String dialString, ImsPhoneCallTracker ct, 173 ImsPhoneCall parent) { 174 createWakeLock(phone.getContext()); 175 acquireWakeLock(); 176 177 mOwner = ct; 178 mHandler = new MyHandler(mOwner.getLooper()); 179 180 mDialString = dialString; 181 182 mAddress = PhoneNumberUtils.extractNetworkPortionAlt(dialString); 183 mPostDialString = PhoneNumberUtils.extractPostDialPortion(dialString); 184 185 //mIndex = -1; 186 187 mIsIncoming = false; 188 mCnapName = null; 189 mCnapNamePresentation = PhoneConstants.PRESENTATION_ALLOWED; 190 mNumberPresentation = PhoneConstants.PRESENTATION_ALLOWED; 191 mCreateTime = System.currentTimeMillis(); 192 193 mParent = parent; 194 parent.attachFake(this, ImsPhoneCall.State.DIALING); 195 196 fetchDtmfToneDelay(phone); 197 } 198 199 public void dispose() { 200 } 201 202 static boolean 203 equalsHandlesNulls (Object a, Object b) { 204 return (a == null) ? (b == null) : a.equals (b); 205 } 206 207 @Override 208 public String getOrigDialString(){ 209 return mDialString; 210 } 211 212 @Override 213 public ImsPhoneCall getCall() { 214 return mParent; 215 } 216 217 @Override 218 public long getDisconnectTime() { 219 return mDisconnectTime; 220 } 221 222 @Override 223 public long getHoldingStartTime() { 224 return mHoldingStartTime; 225 } 226 227 @Override 228 public long getHoldDurationMillis() { 229 if (getState() != ImsPhoneCall.State.HOLDING) { 230 // If not holding, return 0 231 return 0; 232 } else { 233 return SystemClock.elapsedRealtime() - mHoldingStartTime; 234 } 235 } 236 237 @Override 238 public int getDisconnectCause() { 239 return mCause; 240 } 241 242 public void setDisconnectCause(int cause) { 243 mCause = cause; 244 } 245 246 @Override 247 public String getVendorDisconnectCause() { 248 return null; 249 } 250 251 public ImsPhoneCallTracker getOwner () { 252 return mOwner; 253 } 254 255 @Override 256 public ImsPhoneCall.State getState() { 257 if (mDisconnected) { 258 return ImsPhoneCall.State.DISCONNECTED; 259 } else { 260 return super.getState(); 261 } 262 } 263 264 @Override 265 public void hangup() throws CallStateException { 266 if (!mDisconnected) { 267 mOwner.hangup(this); 268 } else { 269 throw new CallStateException ("disconnected"); 270 } 271 } 272 273 @Override 274 public void separate() throws CallStateException { 275 throw new CallStateException ("not supported"); 276 } 277 278 @Override 279 public PostDialState getPostDialState() { 280 return mPostDialState; 281 } 282 283 @Override 284 public void proceedAfterWaitChar() { 285 if (mPostDialState != PostDialState.WAIT) { 286 Rlog.w(LOG_TAG, "ImsPhoneConnection.proceedAfterWaitChar(): Expected " 287 + "getPostDialState() to be WAIT but was " + mPostDialState); 288 return; 289 } 290 291 setPostDialState(PostDialState.STARTED); 292 293 processNextPostDialChar(); 294 } 295 296 @Override 297 public void proceedAfterWildChar(String str) { 298 if (mPostDialState != PostDialState.WILD) { 299 Rlog.w(LOG_TAG, "ImsPhoneConnection.proceedAfterWaitChar(): Expected " 300 + "getPostDialState() to be WILD but was " + mPostDialState); 301 return; 302 } 303 304 setPostDialState(PostDialState.STARTED); 305 306 // make a new postDialString, with the wild char replacement string 307 // at the beginning, followed by the remaining postDialString. 308 309 StringBuilder buf = new StringBuilder(str); 310 buf.append(mPostDialString.substring(mNextPostDialChar)); 311 mPostDialString = buf.toString(); 312 mNextPostDialChar = 0; 313 if (Phone.DEBUG_PHONE) { 314 Rlog.d(LOG_TAG, "proceedAfterWildChar: new postDialString is " + 315 mPostDialString); 316 } 317 318 processNextPostDialChar(); 319 } 320 321 @Override 322 public void cancelPostDial() { 323 setPostDialState(PostDialState.CANCELLED); 324 } 325 326 /** 327 * Called when this Connection is being hung up locally (eg, user pressed "end") 328 */ 329 void 330 onHangupLocal() { 331 mCause = DisconnectCause.LOCAL; 332 } 333 334 /** Called when the connection has been disconnected */ 335 public boolean 336 onDisconnect(int cause) { 337 Rlog.d(LOG_TAG, "onDisconnect: cause=" + cause); 338 if (mCause != DisconnectCause.LOCAL) mCause = cause; 339 return onDisconnect(); 340 } 341 342 /*package*/ boolean 343 onDisconnect() { 344 boolean changed = false; 345 346 if (!mDisconnected) { 347 //mIndex = -1; 348 349 mDisconnectTime = System.currentTimeMillis(); 350 mDuration = SystemClock.elapsedRealtime() - mConnectTimeReal; 351 mDisconnected = true; 352 353 mOwner.mPhone.notifyDisconnect(this); 354 355 if (mParent != null) { 356 changed = mParent.connectionDisconnected(this); 357 } else { 358 Rlog.d(LOG_TAG, "onDisconnect: no parent"); 359 } 360 if (mImsCall != null) mImsCall.close(); 361 mImsCall = null; 362 } 363 releaseWakeLock(); 364 return changed; 365 } 366 367 /** 368 * An incoming or outgoing call has connected 369 */ 370 void 371 onConnectedInOrOut() { 372 mConnectTime = System.currentTimeMillis(); 373 mConnectTimeReal = SystemClock.elapsedRealtime(); 374 mDuration = 0; 375 376 if (Phone.DEBUG_PHONE) { 377 Rlog.d(LOG_TAG, "onConnectedInOrOut: connectTime=" + mConnectTime); 378 } 379 380 if (!mIsIncoming) { 381 // outgoing calls only 382 processNextPostDialChar(); 383 } 384 releaseWakeLock(); 385 } 386 387 /*package*/ void 388 onStartedHolding() { 389 mHoldingStartTime = SystemClock.elapsedRealtime(); 390 } 391 /** 392 * Performs the appropriate action for a post-dial char, but does not 393 * notify application. returns false if the character is invalid and 394 * should be ignored 395 */ 396 private boolean 397 processPostDialChar(char c) { 398 if (PhoneNumberUtils.is12Key(c)) { 399 mOwner.sendDtmf(c, mHandler.obtainMessage(EVENT_DTMF_DONE)); 400 } else if (c == PhoneNumberUtils.PAUSE) { 401 // From TS 22.101: 402 // It continues... 403 // Upon the called party answering the UE shall send the DTMF digits 404 // automatically to the network after a delay of 3 seconds( 20 ). 405 // The digits shall be sent according to the procedures and timing 406 // specified in 3GPP TS 24.008 [13]. The first occurrence of the 407 // "DTMF Control Digits Separator" shall be used by the ME to 408 // distinguish between the addressing digits (i.e. the phone number) 409 // and the DTMF digits. Upon subsequent occurrences of the 410 // separator, 411 // the UE shall pause again for 3 seconds ( 20 ) before sending 412 // any further DTMF digits. 413 mHandler.sendMessageDelayed(mHandler.obtainMessage(EVENT_PAUSE_DONE), 414 PAUSE_DELAY_MILLIS); 415 } else if (c == PhoneNumberUtils.WAIT) { 416 setPostDialState(PostDialState.WAIT); 417 } else if (c == PhoneNumberUtils.WILD) { 418 setPostDialState(PostDialState.WILD); 419 } else { 420 return false; 421 } 422 423 return true; 424 } 425 426 @Override 427 public String 428 getRemainingPostDialString() { 429 if (mPostDialState == PostDialState.CANCELLED 430 || mPostDialState == PostDialState.COMPLETE 431 || mPostDialString == null 432 || mPostDialString.length() <= mNextPostDialChar 433 ) { 434 return ""; 435 } 436 437 return mPostDialString.substring(mNextPostDialChar); 438 } 439 440 @Override 441 protected void finalize() 442 { 443 releaseWakeLock(); 444 } 445 446 private void 447 processNextPostDialChar() { 448 char c = 0; 449 Registrant postDialHandler; 450 451 if (mPostDialState == PostDialState.CANCELLED) { 452 //Rlog.d(LOG_TAG, "##### processNextPostDialChar: postDialState == CANCELLED, bail"); 453 return; 454 } 455 456 if (mPostDialString == null || mPostDialString.length() <= mNextPostDialChar) { 457 setPostDialState(PostDialState.COMPLETE); 458 459 // notifyMessage.arg1 is 0 on complete 460 c = 0; 461 } else { 462 boolean isValid; 463 464 setPostDialState(PostDialState.STARTED); 465 466 c = mPostDialString.charAt(mNextPostDialChar++); 467 468 isValid = processPostDialChar(c); 469 470 if (!isValid) { 471 // Will call processNextPostDialChar 472 mHandler.obtainMessage(EVENT_NEXT_POST_DIAL).sendToTarget(); 473 // Don't notify application 474 Rlog.e(LOG_TAG, "processNextPostDialChar: c=" + c + " isn't valid!"); 475 return; 476 } 477 } 478 479 notifyPostDialListenersNextChar(c); 480 481 // TODO: remove the following code since the handler no longer executes anything. 482 postDialHandler = mOwner.mPhone.mPostDialHandler; 483 484 Message notifyMessage; 485 486 if (postDialHandler != null 487 && (notifyMessage = postDialHandler.messageForRegistrant()) != null) { 488 // The AsyncResult.result is the Connection object 489 PostDialState state = mPostDialState; 490 AsyncResult ar = AsyncResult.forMessage(notifyMessage); 491 ar.result = this; 492 ar.userObj = state; 493 494 // arg1 is the character that was/is being processed 495 notifyMessage.arg1 = c; 496 497 //Rlog.v(LOG_TAG, 498 // "##### processNextPostDialChar: send msg to postDialHandler, arg1=" + c); 499 notifyMessage.sendToTarget(); 500 } 501 } 502 503 /** 504 * Set post dial state and acquire wake lock while switching to "started" 505 * state, the wake lock will be released if state switches out of "started" 506 * state or after WAKE_LOCK_TIMEOUT_MILLIS. 507 * @param s new PostDialState 508 */ 509 private void setPostDialState(PostDialState s) { 510 if (mPostDialState != PostDialState.STARTED 511 && s == PostDialState.STARTED) { 512 acquireWakeLock(); 513 Message msg = mHandler.obtainMessage(EVENT_WAKE_LOCK_TIMEOUT); 514 mHandler.sendMessageDelayed(msg, WAKE_LOCK_TIMEOUT_MILLIS); 515 } else if (mPostDialState == PostDialState.STARTED 516 && s != PostDialState.STARTED) { 517 mHandler.removeMessages(EVENT_WAKE_LOCK_TIMEOUT); 518 releaseWakeLock(); 519 } 520 mPostDialState = s; 521 notifyPostDialListeners(); 522 } 523 524 private void 525 createWakeLock(Context context) { 526 PowerManager pm = (PowerManager) context.getSystemService(Context.POWER_SERVICE); 527 mPartialWakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, LOG_TAG); 528 } 529 530 private void 531 acquireWakeLock() { 532 Rlog.d(LOG_TAG, "acquireWakeLock"); 533 mPartialWakeLock.acquire(); 534 } 535 536 void 537 releaseWakeLock() { 538 synchronized(mPartialWakeLock) { 539 if (mPartialWakeLock.isHeld()) { 540 Rlog.d(LOG_TAG, "releaseWakeLock"); 541 mPartialWakeLock.release(); 542 } 543 } 544 } 545 546 private void fetchDtmfToneDelay(ImsPhone phone) { 547 CarrierConfigManager configMgr = (CarrierConfigManager) 548 phone.getContext().getSystemService(Context.CARRIER_CONFIG_SERVICE); 549 PersistableBundle b = configMgr.getConfigForSubId(phone.getSubId()); 550 if (b != null) { 551 mDtmfToneDelay = b.getInt(CarrierConfigManager.KEY_IMS_DTMF_TONE_DELAY_INT); 552 } 553 } 554 555 @Override 556 public int getNumberPresentation() { 557 return mNumberPresentation; 558 } 559 560 @Override 561 public UUSInfo getUUSInfo() { 562 return mUusInfo; 563 } 564 565 @Override 566 public Connection getOrigConnection() { 567 return null; 568 } 569 570 @Override 571 public boolean isMultiparty() { 572 return mImsCall != null && mImsCall.isMultiparty(); 573 } 574 575 /** 576 * Where {@link #isMultiparty()} is {@code true}, determines if this {@link ImsCall} is the 577 * origin of the conference call (i.e. {@code #isConferenceHost()} is {@code true}), or if this 578 * {@link ImsCall} is a member of a conference hosted on another device. 579 * 580 * @return {@code true} if this call is the origin of the conference call it is a member of, 581 * {@code false} otherwise. 582 */ 583 public boolean isConferenceHost() { 584 if (mImsCall == null) { 585 return false; 586 } 587 return mImsCall.isConferenceHost(); 588 } 589 590 /*package*/ ImsCall getImsCall() { 591 return mImsCall; 592 } 593 594 /*package*/ void setImsCall(ImsCall imsCall) { 595 mImsCall = imsCall; 596 } 597 598 /*package*/ void changeParent(ImsPhoneCall parent) { 599 mParent = parent; 600 } 601 602 /** 603 * @return {@code true} if the {@link ImsPhoneConnection} or its media capabilities have been 604 * changed, and {@code false} otherwise. 605 */ 606 /*package*/ boolean update(ImsCall imsCall, ImsPhoneCall.State state) { 607 if (state == ImsPhoneCall.State.ACTIVE) { 608 // If the state of the call is active, but there is a pending request to the RIL to hold 609 // the call, we will skip this update. This is really a signalling delay or failure 610 // from the RIL, but we will prevent it from going through as we will end up erroneously 611 // making this call active when really it should be on hold. 612 if (imsCall.isPendingHold()) { 613 Rlog.w(LOG_TAG, "update : state is ACTIVE, but call is pending hold, skipping"); 614 return false; 615 } 616 617 if (mParent.getState().isRinging() || mParent.getState().isDialing()) { 618 onConnectedInOrOut(); 619 } 620 621 if (mParent.getState().isRinging() || mParent == mOwner.mBackgroundCall) { 622 //mForegroundCall should be IDLE 623 //when accepting WAITING call 624 //before accept WAITING call, 625 //the ACTIVE call should be held ahead 626 mParent.detach(this); 627 mParent = mOwner.mForegroundCall; 628 mParent.attach(this); 629 } 630 } else if (state == ImsPhoneCall.State.HOLDING) { 631 onStartedHolding(); 632 } 633 634 boolean updateParent = mParent.update(this, imsCall, state); 635 boolean updateWifiState = updateWifiState(); 636 boolean updateAddressDisplay = updateAddressDisplay(imsCall); 637 638 return updateParent || updateWifiState || updateAddressDisplay; 639 } 640 641 @Override 642 public int getPreciseDisconnectCause() { 643 return 0; 644 } 645 646 /** 647 * Notifies this Connection of a request to disconnect a participant of the conference managed 648 * by the connection. 649 * 650 * @param endpoint the {@link android.net.Uri} of the participant to disconnect. 651 */ 652 @Override 653 public void onDisconnectConferenceParticipant(Uri endpoint) { 654 ImsCall imsCall = getImsCall(); 655 if (imsCall == null) { 656 return; 657 } 658 try { 659 imsCall.removeParticipants(new String[]{endpoint.toString()}); 660 } catch (ImsException e) { 661 // No session in place -- no change 662 Rlog.e(LOG_TAG, "onDisconnectConferenceParticipant: no session in place. "+ 663 "Failed to disconnect endpoint = " + endpoint); 664 } 665 } 666 667 /** 668 * Sets the conference connect time. Used when an {@code ImsConference} is created to out of 669 * this phone connection. 670 * 671 * @param conferenceConnectTime The conference connect time. 672 */ 673 public void setConferenceConnectTime(long conferenceConnectTime) { 674 mConferenceConnectTime = conferenceConnectTime; 675 } 676 677 /** 678 * @return The conference connect time. 679 */ 680 public long getConferenceConnectTime() { 681 return mConferenceConnectTime; 682 } 683 684 /** 685 * Check for a change in the address display related fields for the {@link ImsCall}, and 686 * update the {@link ImsPhoneConnection} with this information. 687 * 688 * @param imsCall The call to check for changes in address display fields. 689 * @return Whether the address display fields have been changed. 690 */ 691 private boolean updateAddressDisplay(ImsCall imsCall) { 692 if (imsCall == null) { 693 return false; 694 } 695 696 boolean changed = false; 697 ImsCallProfile callProfile = imsCall.getCallProfile(); 698 if (callProfile != null) { 699 String address = callProfile.getCallExtra(ImsCallProfile.EXTRA_OI); 700 String name = callProfile.getCallExtra(ImsCallProfile.EXTRA_CNA); 701 int nump = ImsCallProfile.OIRToPresentation( 702 callProfile.getCallExtraInt(ImsCallProfile.EXTRA_OIR)); 703 int namep = ImsCallProfile.OIRToPresentation( 704 callProfile.getCallExtraInt(ImsCallProfile.EXTRA_CNAP)); 705 if (Phone.DEBUG_PHONE) { 706 Rlog.d(LOG_TAG, "address = " + address + " name = " + name + 707 " nump = " + nump + " namep = " + namep); 708 } 709 if(equalsHandlesNulls(mAddress, address)) { 710 mAddress = address; 711 changed = true; 712 } 713 if (TextUtils.isEmpty(name)) { 714 if (!TextUtils.isEmpty(mCnapName)) { 715 mCnapName = ""; 716 changed = true; 717 } 718 } else if (!name.equals(mCnapName)) { 719 mCnapName = name; 720 changed = true; 721 } 722 if (mNumberPresentation != nump) { 723 mNumberPresentation = nump; 724 changed = true; 725 } 726 if (mCnapNamePresentation != namep) { 727 mCnapNamePresentation = namep; 728 changed = true; 729 } 730 } 731 return changed; 732 } 733 734 /** 735 * Check for a change in the video capabilities and audio quality for the {@link ImsCall}, and 736 * update the {@link ImsPhoneConnection} with this information. 737 * 738 * @param imsCall The call to check for changes in media capabilities. 739 * @return Whether the media capabilities have been changed. 740 */ 741 public boolean updateMediaCapabilities(ImsCall imsCall) { 742 if (imsCall == null) { 743 return false; 744 } 745 746 boolean changed = false; 747 748 try { 749 // The actual call profile (negotiated between local and peer). 750 ImsCallProfile negotiatedCallProfile = imsCall.getCallProfile(); 751 // The capabilities of the local device. 752 ImsCallProfile localCallProfile = imsCall.getLocalCallProfile(); 753 // The capabilities of the peer device. 754 ImsCallProfile remoteCallProfile = imsCall.getRemoteCallProfile(); 755 756 if (negotiatedCallProfile != null) { 757 int oldVideoState = getVideoState(); 758 int newVideoState = ImsCallProfile 759 .getVideoStateFromImsCallProfile(negotiatedCallProfile); 760 761 if (oldVideoState != newVideoState) { 762 setVideoState(newVideoState); 763 changed = true; 764 } 765 } 766 767 if (localCallProfile != null) { 768 int callType = localCallProfile.mCallType; 769 770 boolean newLocalVideoCapable = callType == ImsCallProfile.CALL_TYPE_VT; 771 if (isLocalVideoCapable() != newLocalVideoCapable) { 772 setLocalVideoCapable(newLocalVideoCapable); 773 changed = true; 774 } 775 } 776 777 if (remoteCallProfile != null) { 778 boolean newRemoteVideoCapable = remoteCallProfile.mCallType 779 == ImsCallProfile.CALL_TYPE_VT; 780 781 if (isRemoteVideoCapable() != newRemoteVideoCapable) { 782 setRemoteVideoCapable(newRemoteVideoCapable); 783 changed = true; 784 } 785 } 786 787 int newAudioQuality = 788 getAudioQualityFromCallProfile(localCallProfile, remoteCallProfile); 789 if (getAudioQuality() != newAudioQuality) { 790 setAudioQuality(newAudioQuality); 791 changed = true; 792 } 793 } catch (ImsException e) { 794 // No session in place -- no change 795 } 796 797 return changed; 798 } 799 800 /** 801 * Check for a change in the wifi state of the ImsPhoneCallTracker and update the 802 * {@link ImsPhoneConnection} with this information. 803 * 804 * @return Whether the ImsPhoneCallTracker's usage of wifi has been changed. 805 */ 806 public boolean updateWifiState() { 807 Rlog.d(LOG_TAG, "updateWifiState: " + mOwner.isVowifiEnabled()); 808 if (isWifi() != mOwner.isVowifiEnabled()) { 809 setWifi(mOwner.isVowifiEnabled()); 810 return true; 811 } 812 return false; 813 } 814 815 /** 816 * Determines the {@link ImsPhoneConnection} audio quality based on the local and remote 817 * {@link ImsCallProfile}. If indicate a HQ audio call if the local stream profile 818 * indicates AMR_WB or EVRC_WB and there is no remote restrict cause. 819 * 820 * @param localCallProfile The local call profile. 821 * @param remoteCallProfile The remote call profile. 822 * @return The audio quality. 823 */ 824 private int getAudioQualityFromCallProfile( 825 ImsCallProfile localCallProfile, ImsCallProfile remoteCallProfile) { 826 if (localCallProfile == null || remoteCallProfile == null 827 || localCallProfile.mMediaProfile == null) { 828 return AUDIO_QUALITY_STANDARD; 829 } 830 831 boolean isHighDef = (localCallProfile.mMediaProfile.mAudioQuality 832 == ImsStreamMediaProfile.AUDIO_QUALITY_AMR_WB 833 || localCallProfile.mMediaProfile.mAudioQuality 834 == ImsStreamMediaProfile.AUDIO_QUALITY_EVRC_WB) 835 && remoteCallProfile.mRestrictCause == ImsCallProfile.CALL_RESTRICT_CAUSE_NONE; 836 return isHighDef ? AUDIO_QUALITY_HIGH_DEFINITION : AUDIO_QUALITY_STANDARD; 837 } 838 839 @Override 840 public Bundle getExtras() { 841 Bundle extras = null; 842 final ImsCall call = getImsCall(); 843 844 if (call != null) { 845 final ImsCallProfile callProfile = call.getCallProfile(); 846 if (callProfile != null) { 847 extras = callProfile.mCallExtras; 848 } 849 } 850 if (extras == null) { 851 if (DBG) Rlog.d(LOG_TAG, "Call profile extras are null."); 852 return null; 853 } 854 return extras; 855 } 856 857 /** 858 * Provides a string representation of the {@link ImsPhoneConnection}. Primarily intended for 859 * use in log statements. 860 * 861 * @return String representation of call. 862 */ 863 @Override 864 public String toString() { 865 StringBuilder sb = new StringBuilder(); 866 sb.append("[ImsPhoneConnection objId: "); 867 sb.append(System.identityHashCode(this)); 868 sb.append(" address:"); 869 sb.append(Log.pii(getAddress())); 870 sb.append(" ImsCall:"); 871 if (mImsCall == null) { 872 sb.append("null"); 873 } else { 874 sb.append(mImsCall); 875 } 876 sb.append("]"); 877 return sb.toString(); 878 } 879} 880 881