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