1 /* 2 * Copyright (C) 2013 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package com.android.internal.telephony.imsphone; 18 19 import android.content.Context; 20 import android.net.Uri; 21 import android.os.AsyncResult; 22 import android.os.Handler; 23 import android.os.Looper; 24 import android.os.Message; 25 import android.os.PowerManager; 26 import android.os.Registrant; 27 import android.os.SystemClock; 28 import android.telecom.Log; 29 import android.telephony.DisconnectCause; 30 import android.telephony.PhoneNumberUtils; 31 import android.telephony.Rlog; 32 33 import com.android.ims.ImsException; 34 import com.android.ims.ImsStreamMediaProfile; 35 import com.android.internal.telephony.CallStateException; 36 import com.android.internal.telephony.Connection; 37 import com.android.internal.telephony.Phone; 38 import com.android.internal.telephony.PhoneConstants; 39 import com.android.internal.telephony.UUSInfo; 40 41 import com.android.ims.ImsCall; 42 import com.android.ims.ImsCallProfile; 43 44 /** 45 * {@hide} 46 */ 47 public class ImsPhoneConnection extends Connection { 48 private static final String LOG_TAG = "ImsPhoneConnection"; 49 private static final boolean DBG = true; 50 51 //***** Instance Variables 52 53 private ImsPhoneCallTracker mOwner; 54 private ImsPhoneCall mParent; 55 private ImsCall mImsCall; 56 57 private String mPostDialString; // outgoing calls only 58 private boolean mDisconnected; 59 60 /* 61 int mIndex; // index in ImsPhoneCallTracker.connections[], -1 if unassigned 62 // The GSM index is 1 + this 63 */ 64 65 /* 66 * These time/timespan values are based on System.currentTimeMillis(), 67 * i.e., "wall clock" time. 68 */ 69 private long mDisconnectTime; 70 71 private int mNextPostDialChar; // index into postDialString 72 73 private int mCause = DisconnectCause.NOT_DISCONNECTED; 74 private PostDialState mPostDialState = PostDialState.NOT_STARTED; 75 private UUSInfo mUusInfo; 76 private Handler mHandler; 77 78 private PowerManager.WakeLock mPartialWakeLock; 79 80 // The cached connect time of the connection when it turns into a conference. 81 private long mConferenceConnectTime = 0; 82 83 //***** Event Constants 84 private static final int EVENT_DTMF_DONE = 1; 85 private static final int EVENT_PAUSE_DONE = 2; 86 private static final int EVENT_NEXT_POST_DIAL = 3; 87 private static final int EVENT_WAKE_LOCK_TIMEOUT = 4; 88 89 //***** Constants 90 private static final int PAUSE_DELAY_MILLIS = 3 * 1000; 91 private static final int WAKE_LOCK_TIMEOUT_MILLIS = 60*1000; 92 93 //***** Inner Classes 94 95 class MyHandler extends Handler { 96 MyHandler(Looper l) {super(l);} 97 98 @Override 99 public void 100 handleMessage(Message msg) { 101 102 switch (msg.what) { 103 case EVENT_NEXT_POST_DIAL: 104 case EVENT_DTMF_DONE: 105 case EVENT_PAUSE_DONE: 106 processNextPostDialChar(); 107 break; 108 case EVENT_WAKE_LOCK_TIMEOUT: 109 releaseWakeLock(); 110 break; 111 } 112 } 113 } 114 115 //***** Constructors 116 117 /** This is probably an MT call */ 118 /*package*/ 119 ImsPhoneConnection(Context context, ImsCall imsCall, ImsPhoneCallTracker ct, ImsPhoneCall parent) { 120 createWakeLock(context); 121 acquireWakeLock(); 122 123 mOwner = ct; 124 mHandler = new MyHandler(mOwner.getLooper()); 125 mImsCall = imsCall; 126 127 if ((imsCall != null) && (imsCall.getCallProfile() != null)) { 128 mAddress = imsCall.getCallProfile().getCallExtra(ImsCallProfile.EXTRA_OI); 129 mCnapName = imsCall.getCallProfile().getCallExtra(ImsCallProfile.EXTRA_CNA); 130 mNumberPresentation = ImsCallProfile.OIRToPresentation( 131 imsCall.getCallProfile().getCallExtraInt(ImsCallProfile.EXTRA_OIR)); 132 mCnapNamePresentation = ImsCallProfile.OIRToPresentation( 133 imsCall.getCallProfile().getCallExtraInt(ImsCallProfile.EXTRA_CNAP)); 134 135 updateMediaCapabilities(imsCall); 136 } else { 137 mNumberPresentation = PhoneConstants.PRESENTATION_UNKNOWN; 138 mCnapNamePresentation = PhoneConstants.PRESENTATION_UNKNOWN; 139 } 140 141 mIsIncoming = true; 142 mCreateTime = System.currentTimeMillis(); 143 mUusInfo = null; 144 145 //mIndex = index; 146 147 mParent = parent; 148 mParent.attach(this, ImsPhoneCall.State.INCOMING); 149 } 150 151 /** This is an MO call, created when dialing */ 152 /*package*/ 153 ImsPhoneConnection(Context context, String dialString, ImsPhoneCallTracker ct, ImsPhoneCall parent) { 154 createWakeLock(context); 155 acquireWakeLock(); 156 157 mOwner = ct; 158 mHandler = new MyHandler(mOwner.getLooper()); 159 160 mDialString = dialString; 161 162 mAddress = PhoneNumberUtils.extractNetworkPortionAlt(dialString); 163 mPostDialString = PhoneNumberUtils.extractPostDialPortion(dialString); 164 165 //mIndex = -1; 166 167 mIsIncoming = false; 168 mCnapName = null; 169 mCnapNamePresentation = PhoneConstants.PRESENTATION_ALLOWED; 170 mNumberPresentation = PhoneConstants.PRESENTATION_ALLOWED; 171 mCreateTime = System.currentTimeMillis(); 172 173 mParent = parent; 174 parent.attachFake(this, ImsPhoneCall.State.DIALING); 175 } 176 177 public void dispose() { 178 } 179 180 static boolean 181 equalsHandlesNulls (Object a, Object b) { 182 return (a == null) ? (b == null) : a.equals (b); 183 } 184 185 @Override 186 public String getOrigDialString(){ 187 return mDialString; 188 } 189 190 @Override 191 public ImsPhoneCall getCall() { 192 return mParent; 193 } 194 195 @Override 196 public long getDisconnectTime() { 197 return mDisconnectTime; 198 } 199 200 @Override 201 public long getHoldingStartTime() { 202 return mHoldingStartTime; 203 } 204 205 @Override 206 public long getHoldDurationMillis() { 207 if (getState() != ImsPhoneCall.State.HOLDING) { 208 // If not holding, return 0 209 return 0; 210 } else { 211 return SystemClock.elapsedRealtime() - mHoldingStartTime; 212 } 213 } 214 215 @Override 216 public int getDisconnectCause() { 217 return mCause; 218 } 219 220 public void setDisconnectCause(int cause) { 221 mCause = cause; 222 } 223 224 public ImsPhoneCallTracker getOwner () { 225 return mOwner; 226 } 227 228 @Override 229 public ImsPhoneCall.State getState() { 230 if (mDisconnected) { 231 return ImsPhoneCall.State.DISCONNECTED; 232 } else { 233 return super.getState(); 234 } 235 } 236 237 @Override 238 public void hangup() throws CallStateException { 239 if (!mDisconnected) { 240 mOwner.hangup(this); 241 } else { 242 throw new CallStateException ("disconnected"); 243 } 244 } 245 246 @Override 247 public void separate() throws CallStateException { 248 throw new CallStateException ("not supported"); 249 } 250 251 @Override 252 public PostDialState getPostDialState() { 253 return mPostDialState; 254 } 255 256 @Override 257 public void proceedAfterWaitChar() { 258 if (mPostDialState != PostDialState.WAIT) { 259 Rlog.w(LOG_TAG, "ImsPhoneConnection.proceedAfterWaitChar(): Expected " 260 + "getPostDialState() to be WAIT but was " + mPostDialState); 261 return; 262 } 263 264 setPostDialState(PostDialState.STARTED); 265 266 processNextPostDialChar(); 267 } 268 269 @Override 270 public void proceedAfterWildChar(String str) { 271 if (mPostDialState != PostDialState.WILD) { 272 Rlog.w(LOG_TAG, "ImsPhoneConnection.proceedAfterWaitChar(): Expected " 273 + "getPostDialState() to be WILD but was " + mPostDialState); 274 return; 275 } 276 277 setPostDialState(PostDialState.STARTED); 278 279 // make a new postDialString, with the wild char replacement string 280 // at the beginning, followed by the remaining postDialString. 281 282 StringBuilder buf = new StringBuilder(str); 283 buf.append(mPostDialString.substring(mNextPostDialChar)); 284 mPostDialString = buf.toString(); 285 mNextPostDialChar = 0; 286 if (Phone.DEBUG_PHONE) { 287 Rlog.d(LOG_TAG, "proceedAfterWildChar: new postDialString is " + 288 mPostDialString); 289 } 290 291 processNextPostDialChar(); 292 } 293 294 @Override 295 public void cancelPostDial() { 296 setPostDialState(PostDialState.CANCELLED); 297 } 298 299 /** 300 * Called when this Connection is being hung up locally (eg, user pressed "end") 301 */ 302 void 303 onHangupLocal() { 304 mCause = DisconnectCause.LOCAL; 305 } 306 307 /** Called when the connection has been disconnected */ 308 public boolean 309 onDisconnect(int cause) { 310 Rlog.d(LOG_TAG, "onDisconnect: cause=" + cause); 311 if (mCause != DisconnectCause.LOCAL) mCause = cause; 312 return onDisconnect(); 313 } 314 315 /*package*/ boolean 316 onDisconnect() { 317 boolean changed = false; 318 319 if (!mDisconnected) { 320 //mIndex = -1; 321 322 mDisconnectTime = System.currentTimeMillis(); 323 mDuration = SystemClock.elapsedRealtime() - mConnectTimeReal; 324 mDisconnected = true; 325 326 mOwner.mPhone.notifyDisconnect(this); 327 328 if (mParent != null) { 329 changed = mParent.connectionDisconnected(this); 330 } else { 331 Rlog.d(LOG_TAG, "onDisconnect: no parent"); 332 } 333 if (mImsCall != null) mImsCall.close(); 334 mImsCall = null; 335 } 336 releaseWakeLock(); 337 return changed; 338 } 339 340 /** 341 * An incoming or outgoing call has connected 342 */ 343 void 344 onConnectedInOrOut() { 345 mConnectTime = System.currentTimeMillis(); 346 mConnectTimeReal = SystemClock.elapsedRealtime(); 347 mDuration = 0; 348 349 if (Phone.DEBUG_PHONE) { 350 Rlog.d(LOG_TAG, "onConnectedInOrOut: connectTime=" + mConnectTime); 351 } 352 353 if (!mIsIncoming) { 354 // outgoing calls only 355 processNextPostDialChar(); 356 } 357 releaseWakeLock(); 358 } 359 360 /*package*/ void 361 onStartedHolding() { 362 mHoldingStartTime = SystemClock.elapsedRealtime(); 363 } 364 /** 365 * Performs the appropriate action for a post-dial char, but does not 366 * notify application. returns false if the character is invalid and 367 * should be ignored 368 */ 369 private boolean 370 processPostDialChar(char c) { 371 if (PhoneNumberUtils.is12Key(c)) { 372 mOwner.sendDtmf(c, mHandler.obtainMessage(EVENT_DTMF_DONE)); 373 } else if (c == PhoneNumberUtils.PAUSE) { 374 // From TS 22.101: 375 // It continues... 376 // Upon the called party answering the UE shall send the DTMF digits 377 // automatically to the network after a delay of 3 seconds( 20 ). 378 // The digits shall be sent according to the procedures and timing 379 // specified in 3GPP TS 24.008 [13]. The first occurrence of the 380 // "DTMF Control Digits Separator" shall be used by the ME to 381 // distinguish between the addressing digits (i.e. the phone number) 382 // and the DTMF digits. Upon subsequent occurrences of the 383 // separator, 384 // the UE shall pause again for 3 seconds ( 20 ) before sending 385 // any further DTMF digits. 386 mHandler.sendMessageDelayed(mHandler.obtainMessage(EVENT_PAUSE_DONE), 387 PAUSE_DELAY_MILLIS); 388 } else if (c == PhoneNumberUtils.WAIT) { 389 setPostDialState(PostDialState.WAIT); 390 } else if (c == PhoneNumberUtils.WILD) { 391 setPostDialState(PostDialState.WILD); 392 } else { 393 return false; 394 } 395 396 return true; 397 } 398 399 @Override 400 public String 401 getRemainingPostDialString() { 402 if (mPostDialState == PostDialState.CANCELLED 403 || mPostDialState == PostDialState.COMPLETE 404 || mPostDialString == null 405 || mPostDialString.length() <= mNextPostDialChar 406 ) { 407 return ""; 408 } 409 410 return mPostDialString.substring(mNextPostDialChar); 411 } 412 413 @Override 414 protected void finalize() 415 { 416 releaseWakeLock(); 417 } 418 419 private void 420 processNextPostDialChar() { 421 char c = 0; 422 Registrant postDialHandler; 423 424 if (mPostDialState == PostDialState.CANCELLED) { 425 //Rlog.d(LOG_TAG, "##### processNextPostDialChar: postDialState == CANCELLED, bail"); 426 return; 427 } 428 429 if (mPostDialString == null || mPostDialString.length() <= mNextPostDialChar) { 430 setPostDialState(PostDialState.COMPLETE); 431 432 // notifyMessage.arg1 is 0 on complete 433 c = 0; 434 } else { 435 boolean isValid; 436 437 setPostDialState(PostDialState.STARTED); 438 439 c = mPostDialString.charAt(mNextPostDialChar++); 440 441 isValid = processPostDialChar(c); 442 443 if (!isValid) { 444 // Will call processNextPostDialChar 445 mHandler.obtainMessage(EVENT_NEXT_POST_DIAL).sendToTarget(); 446 // Don't notify application 447 Rlog.e(LOG_TAG, "processNextPostDialChar: c=" + c + " isn't valid!"); 448 return; 449 } 450 } 451 452 notifyPostDialListenersNextChar(c); 453 454 // TODO: remove the following code since the handler no longer executes anything. 455 postDialHandler = mOwner.mPhone.mPostDialHandler; 456 457 Message notifyMessage; 458 459 if (postDialHandler != null 460 && (notifyMessage = postDialHandler.messageForRegistrant()) != null) { 461 // The AsyncResult.result is the Connection object 462 PostDialState state = mPostDialState; 463 AsyncResult ar = AsyncResult.forMessage(notifyMessage); 464 ar.result = this; 465 ar.userObj = state; 466 467 // arg1 is the character that was/is being processed 468 notifyMessage.arg1 = c; 469 470 //Rlog.v(LOG_TAG, "##### processNextPostDialChar: send msg to postDialHandler, arg1=" + c); 471 notifyMessage.sendToTarget(); 472 } 473 } 474 475 /** 476 * Set post dial state and acquire wake lock while switching to "started" 477 * state, the wake lock will be released if state switches out of "started" 478 * state or after WAKE_LOCK_TIMEOUT_MILLIS. 479 * @param s new PostDialState 480 */ 481 private void setPostDialState(PostDialState s) { 482 if (mPostDialState != PostDialState.STARTED 483 && s == PostDialState.STARTED) { 484 acquireWakeLock(); 485 Message msg = mHandler.obtainMessage(EVENT_WAKE_LOCK_TIMEOUT); 486 mHandler.sendMessageDelayed(msg, WAKE_LOCK_TIMEOUT_MILLIS); 487 } else if (mPostDialState == PostDialState.STARTED 488 && s != PostDialState.STARTED) { 489 mHandler.removeMessages(EVENT_WAKE_LOCK_TIMEOUT); 490 releaseWakeLock(); 491 } 492 mPostDialState = s; 493 notifyPostDialListeners(); 494 } 495 496 private void 497 createWakeLock(Context context) { 498 PowerManager pm = (PowerManager) context.getSystemService(Context.POWER_SERVICE); 499 mPartialWakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, LOG_TAG); 500 } 501 502 private void 503 acquireWakeLock() { 504 Rlog.d(LOG_TAG, "acquireWakeLock"); 505 mPartialWakeLock.acquire(); 506 } 507 508 void 509 releaseWakeLock() { 510 synchronized(mPartialWakeLock) { 511 if (mPartialWakeLock.isHeld()) { 512 Rlog.d(LOG_TAG, "releaseWakeLock"); 513 mPartialWakeLock.release(); 514 } 515 } 516 } 517 518 @Override 519 public int getNumberPresentation() { 520 return mNumberPresentation; 521 } 522 523 @Override 524 public UUSInfo getUUSInfo() { 525 return mUusInfo; 526 } 527 528 @Override 529 public Connection getOrigConnection() { 530 return null; 531 } 532 533 @Override 534 public boolean isMultiparty() { 535 return mImsCall != null && mImsCall.isMultiparty(); 536 } 537 538 /*package*/ ImsCall getImsCall() { 539 return mImsCall; 540 } 541 542 /*package*/ void setImsCall(ImsCall imsCall) { 543 mImsCall = imsCall; 544 } 545 546 /*package*/ void changeParent(ImsPhoneCall parent) { 547 mParent = parent; 548 } 549 550 /** 551 * @return {@code true} if the {@link ImsPhoneConnection} or its media capabilities have been 552 * changed, and {@code false} otherwise. 553 */ 554 /*package*/ boolean update(ImsCall imsCall, ImsPhoneCall.State state) { 555 if (state == ImsPhoneCall.State.ACTIVE) { 556 if (mParent.getState().isRinging() || mParent.getState().isDialing()) { 557 onConnectedInOrOut(); 558 } 559 560 if (mParent.getState().isRinging() || mParent == mOwner.mBackgroundCall) { 561 //mForegroundCall should be IDLE 562 //when accepting WAITING call 563 //before accept WAITING call, 564 //the ACTIVE call should be held ahead 565 mParent.detach(this); 566 mParent = mOwner.mForegroundCall; 567 mParent.attach(this); 568 } 569 } else if (state == ImsPhoneCall.State.HOLDING) { 570 onStartedHolding(); 571 } 572 573 boolean updateParent = mParent.update(this, imsCall, state); 574 boolean updateMediaCapabilities = updateMediaCapabilities(imsCall); 575 return updateParent || updateMediaCapabilities; 576 } 577 578 @Override 579 public int getPreciseDisconnectCause() { 580 return 0; 581 } 582 583 /** 584 * Notifies this Connection of a request to disconnect a participant of the conference managed 585 * by the connection. 586 * 587 * @param endpoint the {@link android.net.Uri} of the participant to disconnect. 588 */ 589 @Override 590 public void onDisconnectConferenceParticipant(Uri endpoint) { 591 ImsCall imsCall = getImsCall(); 592 if (imsCall == null) { 593 return; 594 } 595 try { 596 imsCall.removeParticipants(new String[]{endpoint.toString()}); 597 } catch (ImsException e) { 598 // No session in place -- no change 599 Rlog.e(LOG_TAG, "onDisconnectConferenceParticipant: no session in place. "+ 600 "Failed to disconnect endpoint = " + endpoint); 601 } 602 } 603 604 /** 605 * Sets the conference connect time. Used when an {@code ImsConference} is created to out of 606 * this phone connection. 607 * 608 * @param conferenceConnectTime The conference connect time. 609 */ 610 public void setConferenceConnectTime(long conferenceConnectTime) { 611 mConferenceConnectTime = conferenceConnectTime; 612 } 613 614 /** 615 * @return The conference connect time. 616 */ 617 public long getConferenceConnectTime() { 618 return mConferenceConnectTime; 619 } 620 621 /** 622 * Check for a change in the video capabilities and audio quality for the {@link ImsCall}, and 623 * update the {@link ImsPhoneConnection} with this information. 624 * 625 * @param imsCall The call to check for changes in media capabilities. 626 * @return Whether the media capabilities have been changed. 627 */ 628 private boolean updateMediaCapabilities(ImsCall imsCall) { 629 if (imsCall == null) { 630 return false; 631 } 632 633 boolean changed = false; 634 635 try { 636 // The actual call profile (negotiated between local and peer). 637 ImsCallProfile negotiatedCallProfile = imsCall.getCallProfile(); 638 // The capabilities of the local device. 639 ImsCallProfile localCallProfile = imsCall.getLocalCallProfile(); 640 // The capabilities of the peer device. 641 ImsCallProfile remoteCallProfile = imsCall.getRemoteCallProfile(); 642 643 if (negotiatedCallProfile != null) { 644 int callType = negotiatedCallProfile.mCallType; 645 646 int newVideoState = ImsCallProfile.getVideoStateFromCallType(callType); 647 if (getVideoState() != newVideoState) { 648 setVideoState(newVideoState); 649 changed = true; 650 } 651 } 652 653 if (localCallProfile != null) { 654 int callType = localCallProfile.mCallType; 655 656 boolean newLocalVideoCapable = callType == ImsCallProfile.CALL_TYPE_VT; 657 if (isLocalVideoCapable() != newLocalVideoCapable) { 658 setLocalVideoCapable(newLocalVideoCapable); 659 changed = true; 660 } 661 } 662 663 int newAudioQuality = 664 getAudioQualityFromCallProfile(localCallProfile, remoteCallProfile); 665 if (getAudioQuality() != newAudioQuality) { 666 setAudioQuality(newAudioQuality); 667 changed = true; 668 } 669 } catch (ImsException e) { 670 // No session in place -- no change 671 } 672 673 return changed; 674 } 675 676 /** 677 * Determines the {@link ImsPhoneConnection} audio quality based on the local and remote 678 * {@link ImsCallProfile}. If indicate a HQ audio call if the local stream profile 679 * indicates AMR_WB or EVRC_WB and there is no remote restrict cause. 680 * 681 * @param localCallProfile The local call profile. 682 * @param remoteCallProfile The remote call profile. 683 * @return The audio quality. 684 */ 685 private int getAudioQualityFromCallProfile( 686 ImsCallProfile localCallProfile, ImsCallProfile remoteCallProfile) { 687 if (localCallProfile == null || remoteCallProfile == null 688 || localCallProfile.mMediaProfile == null) { 689 return AUDIO_QUALITY_STANDARD; 690 } 691 692 boolean isHighDef = (localCallProfile.mMediaProfile.mAudioQuality 693 == ImsStreamMediaProfile.AUDIO_QUALITY_AMR_WB 694 || localCallProfile.mMediaProfile.mAudioQuality 695 == ImsStreamMediaProfile.AUDIO_QUALITY_EVRC_WB) 696 && remoteCallProfile.mRestrictCause == ImsCallProfile.CALL_RESTRICT_CAUSE_NONE; 697 return isHighDef ? AUDIO_QUALITY_HIGH_DEFINITION : AUDIO_QUALITY_STANDARD; 698 } 699 700 /** 701 * Provides a string representation of the {@link ImsPhoneConnection}. Primarily intended for 702 * use in log statements. 703 * 704 * @return String representation of call. 705 */ 706 @Override 707 public String toString() { 708 StringBuilder sb = new StringBuilder(); 709 sb.append("[ImsPhoneConnection objId: "); 710 sb.append(System.identityHashCode(this)); 711 sb.append(" address:"); 712 sb.append(Log.pii(getAddress())); 713 sb.append(" ImsCall:"); 714 if (mImsCall == null) { 715 sb.append("null"); 716 } else { 717 sb.append(mImsCall); 718 } 719 sb.append("]"); 720 return sb.toString(); 721 } 722 } 723 724