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