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