GsmCdmaConnection.java revision 0a567c9ed954f295df83c753239646c6f6a04128
1/* 2 * Copyright (C) 2015 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; 18import android.content.Context; 19import android.os.AsyncResult; 20import android.os.Handler; 21import android.os.Looper; 22import android.os.Message; 23import android.os.PersistableBundle; 24import android.os.PowerManager; 25import android.os.Registrant; 26import android.os.SystemClock; 27import android.telephony.CarrierConfigManager; 28import android.telephony.DisconnectCause; 29import android.telephony.Rlog; 30import android.telephony.PhoneNumberUtils; 31import android.telephony.ServiceState; 32import android.text.TextUtils; 33 34import com.android.internal.telephony.cdma.CdmaCallWaitingNotification; 35import com.android.internal.telephony.cdma.CdmaSubscriptionSourceManager; 36import com.android.internal.telephony.uicc.UiccCardApplication; 37import com.android.internal.telephony.uicc.UiccController; 38import com.android.internal.telephony.uicc.IccCardApplicationStatus.AppState; 39 40/** 41 * {@hide} 42 */ 43public class GsmCdmaConnection extends Connection { 44 private static final String LOG_TAG = "GsmCdmaConnection"; 45 private static final boolean DBG = true; 46 private static final boolean VDBG = false; 47 48 //***** Instance Variables 49 50 GsmCdmaCallTracker mOwner; 51 GsmCdmaCall mParent; 52 53 String mPostDialString; // outgoing calls only 54 boolean mDisconnected; 55 56 int mIndex; // index in GsmCdmaCallTracker.connections[], -1 if unassigned 57 // The GsmCdma index is 1 + this 58 59 /* 60 * These time/timespan values are based on System.currentTimeMillis(), 61 * i.e., "wall clock" time. 62 */ 63 long mDisconnectTime; 64 65 int mNextPostDialChar; // index into postDialString 66 67 int mCause = DisconnectCause.NOT_DISCONNECTED; 68 PostDialState mPostDialState = PostDialState.NOT_STARTED; 69 UUSInfo mUusInfo; 70 int mPreciseCause = 0; 71 String mVendorCause; 72 73 Connection mOrigConnection; 74 75 Handler mHandler; 76 77 private PowerManager.WakeLock mPartialWakeLock; 78 79 // The cached delay to be used between DTMF tones fetched from carrier config. 80 private int mDtmfToneDelay = 0; 81 82 //***** Event Constants 83 static final int EVENT_DTMF_DONE = 1; 84 static final int EVENT_PAUSE_DONE = 2; 85 static final int EVENT_NEXT_POST_DIAL = 3; 86 static final int EVENT_WAKE_LOCK_TIMEOUT = 4; 87 static final int EVENT_DTMF_DELAY_DONE = 5; 88 89 //***** Constants 90 static final int PAUSE_DELAY_MILLIS_GSM = 3 * 1000; 91 static final int PAUSE_DELAY_MILLIS_CDMA = 2 * 1000; 92 static final int WAKE_LOCK_TIMEOUT_MILLIS = 60*1000; 93 94 //***** Inner Classes 95 96 class MyHandler extends Handler { 97 MyHandler(Looper l) {super(l);} 98 99 @Override 100 public void 101 handleMessage(Message msg) { 102 103 switch (msg.what) { 104 case EVENT_NEXT_POST_DIAL: 105 case EVENT_DTMF_DELAY_DONE: 106 case EVENT_PAUSE_DONE: 107 processNextPostDialChar(); 108 break; 109 case EVENT_WAKE_LOCK_TIMEOUT: 110 releaseWakeLock(); 111 break; 112 case EVENT_DTMF_DONE: 113 // We may need to add a delay specified by carrier between DTMF tones that are 114 // sent out. 115 mHandler.sendMessageDelayed(mHandler.obtainMessage(EVENT_DTMF_DELAY_DONE), 116 mDtmfToneDelay); 117 break; 118 } 119 } 120 } 121 122 //***** Constructors 123 124 /** This is probably an MT call that we first saw in a CLCC response */ 125 public GsmCdmaConnection (GsmCdmaPhone phone, DriverCall dc, GsmCdmaCallTracker ct, int index) { 126 createWakeLock(phone.getContext()); 127 acquireWakeLock(); 128 129 mOwner = ct; 130 mHandler = new MyHandler(mOwner.getLooper()); 131 132 mAddress = dc.number; 133 134 mIsIncoming = dc.isMT; 135 mCreateTime = System.currentTimeMillis(); 136 mCnapName = dc.name; 137 mCnapNamePresentation = dc.namePresentation; 138 mNumberPresentation = dc.numberPresentation; 139 mUusInfo = dc.uusInfo; 140 141 mIndex = index; 142 143 mParent = parentFromDCState(dc.state); 144 mParent.attach(this, dc); 145 146 fetchDtmfToneDelay(phone); 147 } 148 149 /** This is an MO call, created when dialing */ 150 public GsmCdmaConnection (GsmCdmaPhone phone, String dialString, GsmCdmaCallTracker ct, 151 GsmCdmaCall parent) { 152 createWakeLock(phone.getContext()); 153 acquireWakeLock(); 154 155 mOwner = ct; 156 mHandler = new MyHandler(mOwner.getLooper()); 157 158 if (isPhoneTypeGsm()) { 159 mDialString = dialString; 160 } else { 161 Rlog.d(LOG_TAG, "[GsmCdmaConn] GsmCdmaConnection: dialString=" + maskDialString(dialString)); 162 dialString = formatDialString(dialString); 163 Rlog.d(LOG_TAG, 164 "[GsmCdmaConn] GsmCdmaConnection:formated dialString=" + maskDialString(dialString)); 165 } 166 167 mAddress = PhoneNumberUtils.extractNetworkPortionAlt(dialString); 168 mPostDialString = PhoneNumberUtils.extractPostDialPortion(dialString); 169 170 mIndex = -1; 171 172 mIsIncoming = false; 173 mCnapName = null; 174 mCnapNamePresentation = PhoneConstants.PRESENTATION_ALLOWED; 175 mNumberPresentation = PhoneConstants.PRESENTATION_ALLOWED; 176 mCreateTime = System.currentTimeMillis(); 177 178 if (parent != null) { 179 mParent = parent; 180 if (isPhoneTypeGsm()) { 181 parent.attachFake(this, GsmCdmaCall.State.DIALING); 182 } else { 183 //for the three way call case, not change parent state 184 if (parent.mState == GsmCdmaCall.State.ACTIVE) { 185 parent.attachFake(this, GsmCdmaCall.State.ACTIVE); 186 } else { 187 parent.attachFake(this, GsmCdmaCall.State.DIALING); 188 } 189 190 } 191 } 192 193 fetchDtmfToneDelay(phone); 194 } 195 196 //CDMA 197 /** This is a Call waiting call*/ 198 public GsmCdmaConnection(Context context, CdmaCallWaitingNotification cw, GsmCdmaCallTracker ct, 199 GsmCdmaCall parent) { 200 createWakeLock(context); 201 acquireWakeLock(); 202 203 mOwner = ct; 204 mHandler = new MyHandler(mOwner.getLooper()); 205 mAddress = cw.number; 206 mNumberPresentation = cw.numberPresentation; 207 mCnapName = cw.name; 208 mCnapNamePresentation = cw.namePresentation; 209 mIndex = -1; 210 mIsIncoming = true; 211 mCreateTime = System.currentTimeMillis(); 212 mConnectTime = 0; 213 mParent = parent; 214 parent.attachFake(this, GsmCdmaCall.State.WAITING); 215 } 216 217 218 public void dispose() { 219 clearPostDialListeners(); 220 releaseAllWakeLocks(); 221 } 222 223 static boolean 224 equalsHandlesNulls (Object a, Object b) { 225 return (a == null) ? (b == null) : a.equals (b); 226 } 227 228 //CDMA 229 /** 230 * format original dial string 231 * 1) convert international dialing prefix "+" to 232 * string specified per region 233 * 234 * 2) handle corner cases for PAUSE/WAIT dialing: 235 * 236 * If PAUSE/WAIT sequence at the end, ignore them. 237 * 238 * If consecutive PAUSE/WAIT sequence in the middle of the string, 239 * and if there is any WAIT in PAUSE/WAIT sequence, treat them like WAIT. 240 */ 241 public static String formatDialString(String phoneNumber) { 242 /** 243 * TODO(cleanup): This function should move to PhoneNumberUtils, and 244 * tests should be added. 245 */ 246 247 if (phoneNumber == null) { 248 return null; 249 } 250 int length = phoneNumber.length(); 251 StringBuilder ret = new StringBuilder(); 252 char c; 253 int currIndex = 0; 254 255 while (currIndex < length) { 256 c = phoneNumber.charAt(currIndex); 257 if (isPause(c) || isWait(c)) { 258 if (currIndex < length - 1) { 259 // if PW not at the end 260 int nextIndex = findNextPCharOrNonPOrNonWCharIndex(phoneNumber, currIndex); 261 // If there is non PW char following PW sequence 262 if (nextIndex < length) { 263 char pC = findPOrWCharToAppend(phoneNumber, currIndex, nextIndex); 264 ret.append(pC); 265 // If PW char sequence has more than 2 PW characters, 266 // skip to the last PW character since the sequence already be 267 // converted to WAIT character 268 if (nextIndex > (currIndex + 1)) { 269 currIndex = nextIndex - 1; 270 } 271 } else if (nextIndex == length) { 272 // It means PW characters at the end, ignore 273 currIndex = length - 1; 274 } 275 } 276 } else { 277 ret.append(c); 278 } 279 currIndex++; 280 } 281 return PhoneNumberUtils.cdmaCheckAndProcessPlusCode(ret.toString()); 282 } 283 284 /*package*/ boolean 285 compareTo(DriverCall c) { 286 // On mobile originated (MO) calls, the phone number may have changed 287 // due to a SIM Toolkit call control modification. 288 // 289 // We assume we know when MO calls are created (since we created them) 290 // and therefore don't need to compare the phone number anyway. 291 if (! (mIsIncoming || c.isMT)) return true; 292 293 // A new call appearing by SRVCC may have invalid number 294 // if IMS service is not tightly coupled with cellular modem stack. 295 // Thus we prefer the preexisting handover connection instance. 296 if (isPhoneTypeGsm() && mOrigConnection != null) return true; 297 298 // ... but we can compare phone numbers on MT calls, and we have 299 // no control over when they begin, so we might as well 300 301 String cAddress = PhoneNumberUtils.stringFromStringAndTOA(c.number, c.TOA); 302 return mIsIncoming == c.isMT && equalsHandlesNulls(mAddress, cAddress); 303 } 304 305 @Override 306 public String getOrigDialString(){ 307 return mDialString; 308 } 309 310 @Override 311 public GsmCdmaCall getCall() { 312 return mParent; 313 } 314 315 @Override 316 public long getDisconnectTime() { 317 return mDisconnectTime; 318 } 319 320 @Override 321 public long getHoldDurationMillis() { 322 if (getState() != GsmCdmaCall.State.HOLDING) { 323 // If not holding, return 0 324 return 0; 325 } else { 326 return SystemClock.elapsedRealtime() - mHoldingStartTime; 327 } 328 } 329 330 @Override 331 public int getDisconnectCause() { 332 return mCause; 333 } 334 335 @Override 336 public GsmCdmaCall.State getState() { 337 if (mDisconnected) { 338 return GsmCdmaCall.State.DISCONNECTED; 339 } else { 340 return super.getState(); 341 } 342 } 343 344 @Override 345 public void hangup() throws CallStateException { 346 if (!mDisconnected) { 347 mOwner.hangup(this); 348 } else { 349 throw new CallStateException ("disconnected"); 350 } 351 } 352 353 @Override 354 public void separate() throws CallStateException { 355 if (!mDisconnected) { 356 mOwner.separate(this); 357 } else { 358 throw new CallStateException ("disconnected"); 359 } 360 } 361 362 @Override 363 public PostDialState getPostDialState() { 364 return mPostDialState; 365 } 366 367 @Override 368 public void proceedAfterWaitChar() { 369 if (mPostDialState != PostDialState.WAIT) { 370 Rlog.w(LOG_TAG, "GsmCdmaConnection.proceedAfterWaitChar(): Expected " 371 + "getPostDialState() to be WAIT but was " + mPostDialState); 372 return; 373 } 374 375 setPostDialState(PostDialState.STARTED); 376 377 processNextPostDialChar(); 378 } 379 380 @Override 381 public void proceedAfterWildChar(String str) { 382 if (mPostDialState != PostDialState.WILD) { 383 Rlog.w(LOG_TAG, "GsmCdmaConnection.proceedAfterWaitChar(): Expected " 384 + "getPostDialState() to be WILD but was " + mPostDialState); 385 return; 386 } 387 388 setPostDialState(PostDialState.STARTED); 389 390 // make a new postDialString, with the wild char replacement string 391 // at the beginning, followed by the remaining postDialString. 392 393 StringBuilder buf = new StringBuilder(str); 394 buf.append(mPostDialString.substring(mNextPostDialChar)); 395 mPostDialString = buf.toString(); 396 mNextPostDialChar = 0; 397 if (Phone.DEBUG_PHONE) { 398 log("proceedAfterWildChar: new postDialString is " + 399 mPostDialString); 400 } 401 402 processNextPostDialChar(); 403 } 404 405 @Override 406 public void cancelPostDial() { 407 setPostDialState(PostDialState.CANCELLED); 408 } 409 410 /** 411 * Called when this Connection is being hung up locally (eg, user pressed "end") 412 * Note that at this point, the hangup request has been dispatched to the radio 413 * but no response has yet been received so update() has not yet been called 414 */ 415 void 416 onHangupLocal() { 417 mCause = DisconnectCause.LOCAL; 418 mPreciseCause = 0; 419 mVendorCause = null; 420 } 421 422 /** 423 * Maps RIL call disconnect code to {@link DisconnectCause}. 424 * @param causeCode RIL disconnect code 425 * @return the corresponding value from {@link DisconnectCause} 426 */ 427 int disconnectCauseFromCode(int causeCode) { 428 /** 429 * See 22.001 Annex F.4 for mapping of cause codes 430 * to local tones 431 */ 432 433 switch (causeCode) { 434 case CallFailCause.USER_BUSY: 435 return DisconnectCause.BUSY; 436 437 case CallFailCause.NO_CIRCUIT_AVAIL: 438 case CallFailCause.TEMPORARY_FAILURE: 439 case CallFailCause.SWITCHING_CONGESTION: 440 case CallFailCause.CHANNEL_NOT_AVAIL: 441 case CallFailCause.QOS_NOT_AVAIL: 442 case CallFailCause.BEARER_NOT_AVAIL: 443 return DisconnectCause.CONGESTION; 444 445 case CallFailCause.ACM_LIMIT_EXCEEDED: 446 return DisconnectCause.LIMIT_EXCEEDED; 447 448 case CallFailCause.CALL_BARRED: 449 return DisconnectCause.CALL_BARRED; 450 451 case CallFailCause.FDN_BLOCKED: 452 return DisconnectCause.FDN_BLOCKED; 453 454 case CallFailCause.UNOBTAINABLE_NUMBER: 455 return DisconnectCause.UNOBTAINABLE_NUMBER; 456 457 case CallFailCause.DIAL_MODIFIED_TO_USSD: 458 return DisconnectCause.DIAL_MODIFIED_TO_USSD; 459 460 case CallFailCause.DIAL_MODIFIED_TO_SS: 461 return DisconnectCause.DIAL_MODIFIED_TO_SS; 462 463 case CallFailCause.DIAL_MODIFIED_TO_DIAL: 464 return DisconnectCause.DIAL_MODIFIED_TO_DIAL; 465 466 case CallFailCause.CDMA_LOCKED_UNTIL_POWER_CYCLE: 467 return DisconnectCause.CDMA_LOCKED_UNTIL_POWER_CYCLE; 468 469 case CallFailCause.CDMA_DROP: 470 return DisconnectCause.CDMA_DROP; 471 472 case CallFailCause.CDMA_INTERCEPT: 473 return DisconnectCause.CDMA_INTERCEPT; 474 475 case CallFailCause.CDMA_REORDER: 476 return DisconnectCause.CDMA_REORDER; 477 478 case CallFailCause.CDMA_SO_REJECT: 479 return DisconnectCause.CDMA_SO_REJECT; 480 481 case CallFailCause.CDMA_RETRY_ORDER: 482 return DisconnectCause.CDMA_RETRY_ORDER; 483 484 case CallFailCause.CDMA_ACCESS_FAILURE: 485 return DisconnectCause.CDMA_ACCESS_FAILURE; 486 487 case CallFailCause.CDMA_PREEMPTED: 488 return DisconnectCause.CDMA_PREEMPTED; 489 490 case CallFailCause.CDMA_NOT_EMERGENCY: 491 return DisconnectCause.CDMA_NOT_EMERGENCY; 492 493 case CallFailCause.CDMA_ACCESS_BLOCKED: 494 return DisconnectCause.CDMA_ACCESS_BLOCKED; 495 496 case CallFailCause.ERROR_UNSPECIFIED: 497 case CallFailCause.NORMAL_CLEARING: 498 default: 499 GsmCdmaPhone phone = mOwner.getPhone(); 500 int serviceState = phone.getServiceState().getState(); 501 UiccCardApplication cardApp = phone.getUiccCardApplication(); 502 AppState uiccAppState = (cardApp != null) ? cardApp.getState() : 503 AppState.APPSTATE_UNKNOWN; 504 if (serviceState == ServiceState.STATE_POWER_OFF) { 505 return DisconnectCause.POWER_OFF; 506 } else if (serviceState == ServiceState.STATE_OUT_OF_SERVICE 507 || serviceState == ServiceState.STATE_EMERGENCY_ONLY ) { 508 return DisconnectCause.OUT_OF_SERVICE; 509 } else { 510 if (isPhoneTypeGsm()) { 511 if (uiccAppState != AppState.APPSTATE_READY) { 512 return DisconnectCause.ICC_ERROR; 513 } else if (causeCode == CallFailCause.ERROR_UNSPECIFIED) { 514 if (phone.mSST.mRestrictedState.isCsRestricted()) { 515 return DisconnectCause.CS_RESTRICTED; 516 } else if (phone.mSST.mRestrictedState.isCsEmergencyRestricted()) { 517 return DisconnectCause.CS_RESTRICTED_EMERGENCY; 518 } else if (phone.mSST.mRestrictedState.isCsNormalRestricted()) { 519 return DisconnectCause.CS_RESTRICTED_NORMAL; 520 } else { 521 return DisconnectCause.ERROR_UNSPECIFIED; 522 } 523 } else if (causeCode == CallFailCause.NORMAL_CLEARING) { 524 return DisconnectCause.NORMAL; 525 } else { 526 // If nothing else matches, report unknown call drop reason 527 // to app, not NORMAL call end. 528 return DisconnectCause.ERROR_UNSPECIFIED; 529 } 530 } else { 531 if (phone.mCdmaSubscriptionSource == 532 CdmaSubscriptionSourceManager.SUBSCRIPTION_FROM_RUIM 533 && uiccAppState != AppState.APPSTATE_READY) { 534 return DisconnectCause.ICC_ERROR; 535 } else if (causeCode==CallFailCause.NORMAL_CLEARING) { 536 return DisconnectCause.NORMAL; 537 } else { 538 return DisconnectCause.ERROR_UNSPECIFIED; 539 } 540 } 541 } 542 } 543 } 544 545 /*package*/ void 546 onRemoteDisconnect(int causeCode, String vendorCause) { 547 this.mPreciseCause = causeCode; 548 this.mVendorCause = vendorCause; 549 onDisconnect(disconnectCauseFromCode(causeCode)); 550 } 551 552 /** 553 * Called when the radio indicates the connection has been disconnected. 554 * @param cause call disconnect cause; values are defined in {@link DisconnectCause} 555 */ 556 /*package*/ boolean onDisconnect(int cause) { 557 boolean changed = false; 558 559 mCause = cause; 560 561 if (!mDisconnected) { 562 doDisconnect(); 563 564 if (DBG) Rlog.d(LOG_TAG, "onDisconnect: cause=" + cause); 565 566 mOwner.getPhone().notifyDisconnect(this); 567 568 if (mParent != null) { 569 changed = mParent.connectionDisconnected(this); 570 } 571 572 mOrigConnection = null; 573 } 574 clearPostDialListeners(); 575 releaseWakeLock(); 576 return changed; 577 } 578 579 //CDMA 580 /** Called when the call waiting connection has been hung up */ 581 /*package*/ void 582 onLocalDisconnect() { 583 if (!mDisconnected) { 584 doDisconnect(); 585 if (VDBG) Rlog.d(LOG_TAG, "onLoalDisconnect" ); 586 587 if (mParent != null) { 588 mParent.detach(this); 589 } 590 } 591 releaseWakeLock(); 592 } 593 594 // Returns true if state has changed, false if nothing changed 595 /*package*/ boolean 596 update (DriverCall dc) { 597 GsmCdmaCall newParent; 598 boolean changed = false; 599 boolean wasConnectingInOrOut = isConnectingInOrOut(); 600 boolean wasHolding = (getState() == GsmCdmaCall.State.HOLDING); 601 602 newParent = parentFromDCState(dc.state); 603 604 if (Phone.DEBUG_PHONE) log("parent= " +mParent +", newParent= " + newParent); 605 606 //Ignore dc.number and dc.name in case of a handover connection 607 if (isPhoneTypeGsm() && mOrigConnection != null) { 608 if (Phone.DEBUG_PHONE) log("update: mOrigConnection is not null"); 609 } else { 610 log(" mNumberConverted " + mNumberConverted); 611 if (!equalsHandlesNulls(mAddress, dc.number) && (!mNumberConverted 612 || !equalsHandlesNulls(mConvertedNumber, dc.number))) { 613 if (Phone.DEBUG_PHONE) log("update: phone # changed!"); 614 mAddress = dc.number; 615 changed = true; 616 } 617 } 618 619 // A null cnapName should be the same as "" 620 if (TextUtils.isEmpty(dc.name)) { 621 if (!TextUtils.isEmpty(mCnapName)) { 622 changed = true; 623 mCnapName = ""; 624 } 625 } else if (!dc.name.equals(mCnapName)) { 626 changed = true; 627 mCnapName = dc.name; 628 } 629 630 if (Phone.DEBUG_PHONE) log("--dssds----"+mCnapName); 631 mCnapNamePresentation = dc.namePresentation; 632 mNumberPresentation = dc.numberPresentation; 633 634 if (newParent != mParent) { 635 if (mParent != null) { 636 mParent.detach(this); 637 } 638 newParent.attach(this, dc); 639 mParent = newParent; 640 changed = true; 641 } else { 642 boolean parentStateChange; 643 parentStateChange = mParent.update (this, dc); 644 changed = changed || parentStateChange; 645 } 646 647 /** Some state-transition events */ 648 649 if (Phone.DEBUG_PHONE) log( 650 "update: parent=" + mParent + 651 ", hasNewParent=" + (newParent != mParent) + 652 ", wasConnectingInOrOut=" + wasConnectingInOrOut + 653 ", wasHolding=" + wasHolding + 654 ", isConnectingInOrOut=" + isConnectingInOrOut() + 655 ", changed=" + changed); 656 657 658 if (wasConnectingInOrOut && !isConnectingInOrOut()) { 659 onConnectedInOrOut(); 660 } 661 662 if (changed && !wasHolding && (getState() == GsmCdmaCall.State.HOLDING)) { 663 // We've transitioned into HOLDING 664 onStartedHolding(); 665 } 666 667 return changed; 668 } 669 670 /** 671 * Called when this Connection is in the foregroundCall 672 * when a dial is initiated. 673 * We know we're ACTIVE, and we know we're going to end up 674 * HOLDING in the backgroundCall 675 */ 676 void 677 fakeHoldBeforeDial() { 678 if (mParent != null) { 679 mParent.detach(this); 680 } 681 682 mParent = mOwner.mBackgroundCall; 683 mParent.attachFake(this, GsmCdmaCall.State.HOLDING); 684 685 onStartedHolding(); 686 } 687 688 /*package*/ int 689 getGsmCdmaIndex() throws CallStateException { 690 if (mIndex >= 0) { 691 return mIndex + 1; 692 } else { 693 throw new CallStateException ("GsmCdma index not yet assigned"); 694 } 695 } 696 697 /** 698 * An incoming or outgoing call has connected 699 */ 700 void 701 onConnectedInOrOut() { 702 mConnectTime = System.currentTimeMillis(); 703 mConnectTimeReal = SystemClock.elapsedRealtime(); 704 mDuration = 0; 705 706 // bug #678474: incoming call interpreted as missed call, even though 707 // it sounds like the user has picked up the call. 708 if (Phone.DEBUG_PHONE) { 709 log("onConnectedInOrOut: connectTime=" + mConnectTime); 710 } 711 712 if (!mIsIncoming) { 713 // outgoing calls only 714 processNextPostDialChar(); 715 } else { 716 if (!isPhoneTypeGsm()) { 717 // Only release wake lock for incoming calls, for outgoing calls the wake lock 718 // will be released after any pause-dial is completed 719 releaseWakeLock(); 720 } 721 } 722 723 if (isPhoneTypeGsm()) { 724 releaseWakeLock(); 725 } 726 } 727 728 private void 729 doDisconnect() { 730 mIndex = -1; 731 mDisconnectTime = System.currentTimeMillis(); 732 mDuration = SystemClock.elapsedRealtime() - mConnectTimeReal; 733 mDisconnected = true; 734 clearPostDialListeners(); 735 } 736 737 /*package*/ void 738 onStartedHolding() { 739 mHoldingStartTime = SystemClock.elapsedRealtime(); 740 } 741 742 /** 743 * Performs the appropriate action for a post-dial char, but does not 744 * notify application. returns false if the character is invalid and 745 * should be ignored 746 */ 747 private boolean 748 processPostDialChar(char c) { 749 if (PhoneNumberUtils.is12Key(c)) { 750 mOwner.mCi.sendDtmf(c, mHandler.obtainMessage(EVENT_DTMF_DONE)); 751 } else if (isPause(c)) { 752 if (!isPhoneTypeGsm()) { 753 setPostDialState(PostDialState.PAUSE); 754 } 755 // From TS 22.101: 756 // It continues... 757 // Upon the called party answering the UE shall send the DTMF digits 758 // automatically to the network after a delay of 3 seconds( 20 ). 759 // The digits shall be sent according to the procedures and timing 760 // specified in 3GPP TS 24.008 [13]. The first occurrence of the 761 // "DTMF Control Digits Separator" shall be used by the ME to 762 // distinguish between the addressing digits (i.e. the phone number) 763 // and the DTMF digits. Upon subsequent occurrences of the 764 // separator, 765 // the UE shall pause again for 3 seconds ( 20 ) before sending 766 // any further DTMF digits. 767 mHandler.sendMessageDelayed(mHandler.obtainMessage(EVENT_PAUSE_DONE), 768 isPhoneTypeGsm() ? PAUSE_DELAY_MILLIS_GSM: PAUSE_DELAY_MILLIS_CDMA); 769 } else if (isWait(c)) { 770 setPostDialState(PostDialState.WAIT); 771 } else if (isWild(c)) { 772 setPostDialState(PostDialState.WILD); 773 } else { 774 return false; 775 } 776 777 return true; 778 } 779 780 @Override 781 public String 782 getRemainingPostDialString() { 783 if (mPostDialState == PostDialState.CANCELLED 784 || mPostDialState == PostDialState.COMPLETE 785 || mPostDialString == null 786 || mPostDialString.length() <= mNextPostDialChar) { 787 return ""; 788 } 789 790 String subStr = mPostDialString.substring(mNextPostDialChar); 791 if (!isPhoneTypeGsm()) { 792 if (subStr != null) { 793 int wIndex = subStr.indexOf(PhoneNumberUtils.WAIT); 794 int pIndex = subStr.indexOf(PhoneNumberUtils.PAUSE); 795 796 if (wIndex > 0 && (wIndex < pIndex || pIndex <= 0)) { 797 subStr = subStr.substring(0, wIndex); 798 } else if (pIndex > 0) { 799 subStr = subStr.substring(0, pIndex); 800 } 801 } 802 } 803 return subStr; 804 } 805 806 //CDMA 807 public void updateParent(GsmCdmaCall oldParent, GsmCdmaCall newParent){ 808 if (newParent != oldParent) { 809 if (oldParent != null) { 810 oldParent.detach(this); 811 } 812 newParent.attachFake(this, GsmCdmaCall.State.ACTIVE); 813 mParent = newParent; 814 } 815 } 816 817 @Override 818 protected void finalize() 819 { 820 /** 821 * It is understood that This finializer is not guaranteed 822 * to be called and the release lock call is here just in 823 * case there is some path that doesn't call onDisconnect 824 * and or onConnectedInOrOut. 825 */ 826 if (mPartialWakeLock.isHeld()) { 827 Rlog.e(LOG_TAG, "[GsmCdmaConn] UNEXPECTED; mPartialWakeLock is held when finalizing."); 828 } 829 clearPostDialListeners(); 830 releaseWakeLock(); 831 } 832 833 private void 834 processNextPostDialChar() { 835 char c = 0; 836 Registrant postDialHandler; 837 838 if (mPostDialState == PostDialState.CANCELLED) { 839 if (!isPhoneTypeGsm()) { 840 releaseWakeLock(); 841 } 842 //Rlog.v("GsmCdma", "##### processNextPostDialChar: postDialState == CANCELLED, bail"); 843 return; 844 } 845 846 if (mPostDialString == null || 847 mPostDialString.length() <= mNextPostDialChar) { 848 setPostDialState(PostDialState.COMPLETE); 849 850 if (!isPhoneTypeGsm()) { 851 // We were holding a wake lock until pause-dial was complete, so give it up now 852 releaseWakeLock(); 853 } 854 855 // notifyMessage.arg1 is 0 on complete 856 c = 0; 857 } else { 858 boolean isValid; 859 860 setPostDialState(PostDialState.STARTED); 861 862 c = mPostDialString.charAt(mNextPostDialChar++); 863 864 isValid = processPostDialChar(c); 865 866 if (!isValid) { 867 // Will call processNextPostDialChar 868 mHandler.obtainMessage(EVENT_NEXT_POST_DIAL).sendToTarget(); 869 // Don't notify application 870 Rlog.e(LOG_TAG, "processNextPostDialChar: c=" + c + " isn't valid!"); 871 return; 872 } 873 } 874 875 notifyPostDialListenersNextChar(c); 876 877 // TODO: remove the following code since the handler no longer executes anything. 878 postDialHandler = mOwner.getPhone().mPostDialHandler; 879 880 Message notifyMessage; 881 882 if (postDialHandler != null 883 && (notifyMessage = postDialHandler.messageForRegistrant()) != null) { 884 // The AsyncResult.result is the Connection object 885 PostDialState state = mPostDialState; 886 AsyncResult ar = AsyncResult.forMessage(notifyMessage); 887 ar.result = this; 888 ar.userObj = state; 889 890 // arg1 is the character that was/is being processed 891 notifyMessage.arg1 = c; 892 893 //Rlog.v("GsmCdma", "##### processNextPostDialChar: send msg to postDialHandler, arg1=" + c); 894 notifyMessage.sendToTarget(); 895 } 896 } 897 898 /** "connecting" means "has never been ACTIVE" for both incoming 899 * and outgoing calls 900 */ 901 private boolean 902 isConnectingInOrOut() { 903 return mParent == null || mParent == mOwner.mRingingCall 904 || mParent.mState == GsmCdmaCall.State.DIALING 905 || mParent.mState == GsmCdmaCall.State.ALERTING; 906 } 907 908 private GsmCdmaCall 909 parentFromDCState (DriverCall.State state) { 910 switch (state) { 911 case ACTIVE: 912 case DIALING: 913 case ALERTING: 914 return mOwner.mForegroundCall; 915 //break; 916 917 case HOLDING: 918 return mOwner.mBackgroundCall; 919 //break; 920 921 case INCOMING: 922 case WAITING: 923 return mOwner.mRingingCall; 924 //break; 925 926 default: 927 throw new RuntimeException("illegal call state: " + state); 928 } 929 } 930 931 /** 932 * Set post dial state and acquire wake lock while switching to "started" 933 * state, the wake lock will be released if state switches out of "started" 934 * state or after WAKE_LOCK_TIMEOUT_MILLIS. 935 * @param s new PostDialState 936 */ 937 private void setPostDialState(PostDialState s) { 938 if (isPhoneTypeGsm()) { 939 if (mPostDialState != PostDialState.STARTED 940 && s == PostDialState.STARTED) { 941 acquireWakeLock(); 942 Message msg = mHandler.obtainMessage(EVENT_WAKE_LOCK_TIMEOUT); 943 mHandler.sendMessageDelayed(msg, WAKE_LOCK_TIMEOUT_MILLIS); 944 } else if (mPostDialState == PostDialState.STARTED 945 && s != PostDialState.STARTED) { 946 mHandler.removeMessages(EVENT_WAKE_LOCK_TIMEOUT); 947 releaseWakeLock(); 948 } 949 } else { 950 if (s == PostDialState.STARTED || 951 s == PostDialState.PAUSE) { 952 synchronized (mPartialWakeLock) { 953 if (mPartialWakeLock.isHeld()) { 954 mHandler.removeMessages(EVENT_WAKE_LOCK_TIMEOUT); 955 } else { 956 acquireWakeLock(); 957 } 958 Message msg = mHandler.obtainMessage(EVENT_WAKE_LOCK_TIMEOUT); 959 mHandler.sendMessageDelayed(msg, WAKE_LOCK_TIMEOUT_MILLIS); 960 } 961 } else { 962 mHandler.removeMessages(EVENT_WAKE_LOCK_TIMEOUT); 963 releaseWakeLock(); 964 } 965 } 966 mPostDialState = s; 967 notifyPostDialListeners(); 968 } 969 970 private void 971 createWakeLock(Context context) { 972 PowerManager pm = (PowerManager) context.getSystemService(Context.POWER_SERVICE); 973 mPartialWakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, LOG_TAG); 974 } 975 976 private void 977 acquireWakeLock() { 978 log("acquireWakeLock"); 979 mPartialWakeLock.acquire(); 980 } 981 982 private void 983 releaseWakeLock() { 984 synchronized(mPartialWakeLock) { 985 if (mPartialWakeLock.isHeld()) { 986 log("releaseWakeLock"); 987 mPartialWakeLock.release(); 988 } 989 } 990 } 991 992 private void 993 releaseAllWakeLocks() { 994 synchronized(mPartialWakeLock) { 995 while (mPartialWakeLock.isHeld()) { 996 mPartialWakeLock.release(); 997 } 998 } 999 } 1000 1001 private static boolean isPause(char c) { 1002 return c == PhoneNumberUtils.PAUSE; 1003 } 1004 1005 private static boolean isWait(char c) { 1006 return c == PhoneNumberUtils.WAIT; 1007 } 1008 1009 private static boolean isWild(char c) { 1010 return c == PhoneNumberUtils.WILD; 1011 } 1012 1013 //CDMA 1014 // This function is to find the next PAUSE character index if 1015 // multiple pauses in a row. Otherwise it finds the next non PAUSE or 1016 // non WAIT character index. 1017 private static int 1018 findNextPCharOrNonPOrNonWCharIndex(String phoneNumber, int currIndex) { 1019 boolean wMatched = isWait(phoneNumber.charAt(currIndex)); 1020 int index = currIndex + 1; 1021 int length = phoneNumber.length(); 1022 while (index < length) { 1023 char cNext = phoneNumber.charAt(index); 1024 // if there is any W inside P/W sequence,mark it 1025 if (isWait(cNext)) { 1026 wMatched = true; 1027 } 1028 // if any characters other than P/W chars after P/W sequence 1029 // we break out the loop and append the correct 1030 if (!isWait(cNext) && !isPause(cNext)) { 1031 break; 1032 } 1033 index++; 1034 } 1035 1036 // It means the PAUSE character(s) is in the middle of dial string 1037 // and it needs to be handled one by one. 1038 if ((index < length) && (index > (currIndex + 1)) && 1039 ((wMatched == false) && isPause(phoneNumber.charAt(currIndex)))) { 1040 return (currIndex + 1); 1041 } 1042 return index; 1043 } 1044 1045 //CDMA 1046 // This function returns either PAUSE or WAIT character to append. 1047 // It is based on the next non PAUSE/WAIT character in the phoneNumber and the 1048 // index for the current PAUSE/WAIT character 1049 private static char 1050 findPOrWCharToAppend(String phoneNumber, int currPwIndex, int nextNonPwCharIndex) { 1051 char c = phoneNumber.charAt(currPwIndex); 1052 char ret; 1053 1054 // Append the PW char 1055 ret = (isPause(c)) ? PhoneNumberUtils.PAUSE : PhoneNumberUtils.WAIT; 1056 1057 // If the nextNonPwCharIndex is greater than currPwIndex + 1, 1058 // it means the PW sequence contains not only P characters. 1059 // Since for the sequence that only contains P character, 1060 // the P character is handled one by one, the nextNonPwCharIndex 1061 // equals to currPwIndex + 1. 1062 // In this case, skip P, append W. 1063 if (nextNonPwCharIndex > (currPwIndex + 1)) { 1064 ret = PhoneNumberUtils.WAIT; 1065 } 1066 return ret; 1067 } 1068 1069 private String maskDialString(String dialString) { 1070 if (VDBG) { 1071 return dialString; 1072 } 1073 1074 return "<MASKED>"; 1075 } 1076 1077 private void fetchDtmfToneDelay(GsmCdmaPhone phone) { 1078 CarrierConfigManager configMgr = (CarrierConfigManager) 1079 phone.getContext().getSystemService(Context.CARRIER_CONFIG_SERVICE); 1080 PersistableBundle b = configMgr.getConfigForSubId(phone.getSubId()); 1081 if (b != null) { 1082 if (phone.getPhoneType() == PhoneConstants.PHONE_TYPE_GSM) { 1083 mDtmfToneDelay = b.getInt(CarrierConfigManager.KEY_GSM_DTMF_TONE_DELAY_INT); 1084 } else { 1085 mDtmfToneDelay = b.getInt(CarrierConfigManager.KEY_CDMA_DTMF_TONE_DELAY_INT); 1086 } 1087 } 1088 } 1089 1090 private boolean isPhoneTypeGsm() { 1091 return mOwner.getPhone().getPhoneType() == PhoneConstants.PHONE_TYPE_GSM; 1092 } 1093 1094 private void log(String msg) { 1095 Rlog.d(LOG_TAG, "[GsmCdmaConn] " + msg); 1096 } 1097 1098 @Override 1099 public int getNumberPresentation() { 1100 return mNumberPresentation; 1101 } 1102 1103 @Override 1104 public UUSInfo getUUSInfo() { 1105 return mUusInfo; 1106 } 1107 1108 public int getPreciseDisconnectCause() { 1109 return mPreciseCause; 1110 } 1111 1112 @Override 1113 public String getVendorDisconnectCause() { 1114 return mVendorCause; 1115 } 1116 1117 @Override 1118 public void migrateFrom(Connection c) { 1119 if (c == null) return; 1120 1121 super.migrateFrom(c); 1122 1123 this.mUusInfo = c.getUUSInfo(); 1124 1125 this.setUserData(c.getUserData()); 1126 } 1127 1128 @Override 1129 public Connection getOrigConnection() { 1130 return mOrigConnection; 1131 } 1132 1133 @Override 1134 public boolean isMultiparty() { 1135 if (mOrigConnection != null) { 1136 return mOrigConnection.isMultiparty(); 1137 } 1138 1139 return false; 1140 } 1141} 1142