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