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