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