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); 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); 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); 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 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.ACCESS_CLASS_BLOCKED: 569 case CallFailCause.ERROR_UNSPECIFIED: 570 case CallFailCause.NORMAL_CLEARING: 571 default: 572 GsmCdmaPhone phone = mOwner.getPhone(); 573 int serviceState = phone.getServiceState().getState(); 574 UiccCardApplication cardApp = phone.getUiccCardApplication(); 575 AppState uiccAppState = (cardApp != null) ? cardApp.getState() : 576 AppState.APPSTATE_UNKNOWN; 577 if (serviceState == ServiceState.STATE_POWER_OFF) { 578 return DisconnectCause.POWER_OFF; 579 } 580 if (!isEmergencyCall()) { 581 // Only send OUT_OF_SERVICE if it is not an emergency call. We can still 582 // technically be in STATE_OUT_OF_SERVICE or STATE_EMERGENCY_ONLY during 583 // an emergency call and when it ends, we do not want to mistakenly generate 584 // an OUT_OF_SERVICE disconnect cause during normal call ending. 585 if ((serviceState == ServiceState.STATE_OUT_OF_SERVICE 586 || serviceState == ServiceState.STATE_EMERGENCY_ONLY)) { 587 return DisconnectCause.OUT_OF_SERVICE; 588 } 589 // If we are placing an emergency call and the SIM is currently PIN/PUK 590 // locked the AppState will always not be equal to APPSTATE_READY. 591 if (uiccAppState != AppState.APPSTATE_READY) { 592 if (isPhoneTypeGsm()) { 593 return DisconnectCause.ICC_ERROR; 594 } else { // CDMA 595 if (phone.mCdmaSubscriptionSource == 596 CdmaSubscriptionSourceManager.SUBSCRIPTION_FROM_RUIM) { 597 return DisconnectCause.ICC_ERROR; 598 } 599 } 600 } 601 } 602 if (isPhoneTypeGsm()) { 603 if (causeCode == CallFailCause.ERROR_UNSPECIFIED || 604 causeCode == CallFailCause.ACCESS_CLASS_BLOCKED ) { 605 if (phone.mSST.mRestrictedState.isCsRestricted()) { 606 return DisconnectCause.CS_RESTRICTED; 607 } else if (phone.mSST.mRestrictedState.isCsEmergencyRestricted()) { 608 return DisconnectCause.CS_RESTRICTED_EMERGENCY; 609 } else if (phone.mSST.mRestrictedState.isCsNormalRestricted()) { 610 return DisconnectCause.CS_RESTRICTED_NORMAL; 611 } 612 } 613 } 614 if (causeCode == CallFailCause.NORMAL_CLEARING) { 615 return DisconnectCause.NORMAL; 616 } 617 // If nothing else matches, report unknown call drop reason 618 // to app, not NORMAL call end. 619 return DisconnectCause.ERROR_UNSPECIFIED; 620 } 621 } 622 623 /*package*/ void onRemoteDisconnect(int causeCode, String vendorCause)624 onRemoteDisconnect(int causeCode, String vendorCause) { 625 this.mPreciseCause = causeCode; 626 this.mVendorCause = vendorCause; 627 onDisconnect(disconnectCauseFromCode(causeCode)); 628 } 629 630 /** 631 * Called when the radio indicates the connection has been disconnected. 632 * @param cause call disconnect cause; values are defined in {@link DisconnectCause} 633 */ 634 @Override onDisconnect(int cause)635 public boolean onDisconnect(int cause) { 636 boolean changed = false; 637 638 mCause = cause; 639 640 if (!mDisconnected) { 641 doDisconnect(); 642 643 if (DBG) Rlog.d(LOG_TAG, "onDisconnect: cause=" + cause); 644 645 mOwner.getPhone().notifyDisconnect(this); 646 notifyDisconnect(cause); 647 648 if (mParent != null) { 649 changed = mParent.connectionDisconnected(this); 650 } 651 652 mOrigConnection = null; 653 } 654 clearPostDialListeners(); 655 releaseWakeLock(); 656 return changed; 657 } 658 659 //CDMA 660 /** Called when the call waiting connection has been hung up */ 661 /*package*/ void onLocalDisconnect()662 onLocalDisconnect() { 663 if (!mDisconnected) { 664 doDisconnect(); 665 if (VDBG) Rlog.d(LOG_TAG, "onLoalDisconnect" ); 666 667 if (mParent != null) { 668 mParent.detach(this); 669 } 670 } 671 releaseWakeLock(); 672 } 673 674 // Returns true if state has changed, false if nothing changed 675 public boolean update(DriverCall dc)676 update (DriverCall dc) { 677 GsmCdmaCall newParent; 678 boolean changed = false; 679 boolean wasConnectingInOrOut = isConnectingInOrOut(); 680 boolean wasHolding = (getState() == GsmCdmaCall.State.HOLDING); 681 682 newParent = parentFromDCState(dc.state); 683 684 if (Phone.DEBUG_PHONE) log("parent= " +mParent +", newParent= " + newParent); 685 686 //Ignore dc.number and dc.name in case of a handover connection 687 if (isPhoneTypeGsm() && mOrigConnection != null) { 688 if (Phone.DEBUG_PHONE) log("update: mOrigConnection is not null"); 689 } else if (isIncoming()) { 690 if (!equalsBaseDialString(mAddress, dc.number) && (!mNumberConverted 691 || !equalsBaseDialString(mConvertedNumber, dc.number))) { 692 if (Phone.DEBUG_PHONE) log("update: phone # changed!"); 693 mAddress = dc.number; 694 changed = true; 695 } 696 } 697 698 int newAudioQuality = getAudioQualityFromDC(dc.audioQuality); 699 if (getAudioQuality() != newAudioQuality) { 700 if (Phone.DEBUG_PHONE) { 701 log("update: audioQuality # changed!: " 702 + (newAudioQuality == Connection.AUDIO_QUALITY_HIGH_DEFINITION 703 ? "high" : "standard")); 704 } 705 setAudioQuality(newAudioQuality); 706 changed = true; 707 } 708 709 // Metrics for audio codec 710 if (dc.audioQuality != mAudioCodec) { 711 mAudioCodec = dc.audioQuality; 712 mMetrics.writeAudioCodecGsmCdma(mOwner.getPhone().getPhoneId(), dc.audioQuality); 713 mOwner.getPhone().getVoiceCallSessionStats().onAudioCodecChanged(this, dc.audioQuality); 714 } 715 716 String forwardedNumber = TextUtils.isEmpty(dc.forwardedNumber) ? null : dc.forwardedNumber; 717 Rlog.i(LOG_TAG, "update: forwardedNumber=" + Rlog.pii(LOG_TAG, forwardedNumber)); 718 ArrayList<String> forwardedNumbers = forwardedNumber == null ? null : 719 new ArrayList<>(Collections.singletonList(dc.forwardedNumber)); 720 if (!equalsHandlesNulls(mForwardedNumber, forwardedNumbers)) { 721 if (Phone.DEBUG_PHONE) log("update: mForwardedNumber, # changed"); 722 mForwardedNumber = forwardedNumbers; 723 changed = true; 724 } 725 726 // A null cnapName should be the same as "" 727 if (TextUtils.isEmpty(dc.name)) { 728 if (!TextUtils.isEmpty(mCnapName)) { 729 changed = true; 730 mCnapName = ""; 731 } 732 } else if (!dc.name.equals(mCnapName)) { 733 changed = true; 734 mCnapName = dc.name; 735 } 736 737 if (Phone.DEBUG_PHONE) log("--dssds----"+mCnapName); 738 mCnapNamePresentation = dc.namePresentation; 739 mNumberPresentation = dc.numberPresentation; 740 741 if (newParent != mParent) { 742 if (mParent != null) { 743 mParent.detach(this); 744 } 745 newParent.attach(this, dc); 746 mParent = newParent; 747 changed = true; 748 } else { 749 boolean parentStateChange; 750 parentStateChange = mParent.update (this, dc); 751 changed = changed || parentStateChange; 752 } 753 754 /** Some state-transition events */ 755 756 if (Phone.DEBUG_PHONE) log( 757 "update: parent=" + mParent + 758 ", hasNewParent=" + (newParent != mParent) + 759 ", wasConnectingInOrOut=" + wasConnectingInOrOut + 760 ", wasHolding=" + wasHolding + 761 ", isConnectingInOrOut=" + isConnectingInOrOut() + 762 ", changed=" + changed); 763 764 765 if (wasConnectingInOrOut && !isConnectingInOrOut()) { 766 onConnectedInOrOut(); 767 } 768 769 if (changed && !wasHolding && (getState() == GsmCdmaCall.State.HOLDING)) { 770 // We've transitioned into HOLDING 771 onStartedHolding(); 772 } 773 774 return changed; 775 } 776 777 /** 778 * Called when this Connection is in the foregroundCall 779 * when a dial is initiated. 780 * We know we're ACTIVE, and we know we're going to end up 781 * HOLDING in the backgroundCall 782 */ 783 void fakeHoldBeforeDial()784 fakeHoldBeforeDial() { 785 if (mParent != null) { 786 mParent.detach(this); 787 } 788 789 mParent = mOwner.mBackgroundCall; 790 mParent.attachFake(this, GsmCdmaCall.State.HOLDING); 791 792 onStartedHolding(); 793 } 794 795 /*package*/ int getGsmCdmaIndex()796 getGsmCdmaIndex() throws CallStateException { 797 if (mIndex >= 0) { 798 return mIndex + 1; 799 } else { 800 throw new CallStateException ("GsmCdma index not yet assigned"); 801 } 802 } 803 804 /** 805 * An incoming or outgoing call has connected 806 */ 807 @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) 808 void onConnectedInOrOut()809 onConnectedInOrOut() { 810 mConnectTime = System.currentTimeMillis(); 811 mConnectTimeReal = SystemClock.elapsedRealtime(); 812 mDuration = 0; 813 814 // bug #678474: incoming call interpreted as missed call, even though 815 // it sounds like the user has picked up the call. 816 if (Phone.DEBUG_PHONE) { 817 log("onConnectedInOrOut: connectTime=" + mConnectTime); 818 } 819 820 if (!mIsIncoming) { 821 // outgoing calls only 822 processNextPostDialChar(); 823 } else { 824 // Only release wake lock for incoming calls, for outgoing calls the wake lock 825 // will be released after any pause-dial is completed 826 releaseWakeLock(); 827 } 828 } 829 830 /** 831 * We have completed the migration of another connection to this GsmCdmaConnection (for example, 832 * in the case of SRVCC) and not still DIALING/ALERTING/INCOMING/WAITING. 833 */ onConnectedConnectionMigrated()834 void onConnectedConnectionMigrated() { 835 // We can release the wakelock in this case, the migrated call is not still 836 // DIALING/ALERTING/INCOMING/WAITING. 837 releaseWakeLock(); 838 } 839 840 private void doDisconnect()841 doDisconnect() { 842 mIndex = -1; 843 mDisconnectTime = System.currentTimeMillis(); 844 mDuration = SystemClock.elapsedRealtime() - mConnectTimeReal; 845 mDisconnected = true; 846 clearPostDialListeners(); 847 } 848 849 /*package*/ void onStartedHolding()850 onStartedHolding() { 851 mHoldingStartTime = SystemClock.elapsedRealtime(); 852 } 853 854 /** 855 * Performs the appropriate action for a post-dial char, but does not 856 * notify application. returns false if the character is invalid and 857 * should be ignored 858 */ 859 private boolean processPostDialChar(char c)860 processPostDialChar(char c) { 861 if (PhoneNumberUtils.is12Key(c)) { 862 mOwner.mCi.sendDtmf(c, mHandler.obtainMessage(EVENT_DTMF_DONE)); 863 } else if (isPause(c)) { 864 if (!isPhoneTypeGsm()) { 865 setPostDialState(PostDialState.PAUSE); 866 } 867 // From TS 22.101: 868 // It continues... 869 // Upon the called party answering the UE shall send the DTMF digits 870 // automatically to the network after a delay of 3 seconds( 20 ). 871 // The digits shall be sent according to the procedures and timing 872 // specified in 3GPP TS 24.008 [13]. The first occurrence of the 873 // "DTMF Control Digits Separator" shall be used by the ME to 874 // distinguish between the addressing digits (i.e. the phone number) 875 // and the DTMF digits. Upon subsequent occurrences of the 876 // separator, 877 // the UE shall pause again for 3 seconds ( 20 ) before sending 878 // any further DTMF digits. 879 mHandler.sendMessageDelayed(mHandler.obtainMessage(EVENT_PAUSE_DONE), 880 isPhoneTypeGsm() ? PAUSE_DELAY_MILLIS_GSM: PAUSE_DELAY_MILLIS_CDMA); 881 } else if (isWait(c)) { 882 setPostDialState(PostDialState.WAIT); 883 } else if (isWild(c)) { 884 setPostDialState(PostDialState.WILD); 885 } else { 886 return false; 887 } 888 889 return true; 890 } 891 892 @Override 893 public String getRemainingPostDialString()894 getRemainingPostDialString() { 895 String subStr = super.getRemainingPostDialString(); 896 if (!isPhoneTypeGsm() && !TextUtils.isEmpty(subStr)) { 897 int wIndex = subStr.indexOf(PhoneNumberUtils.WAIT); 898 int pIndex = subStr.indexOf(PhoneNumberUtils.PAUSE); 899 900 if (wIndex > 0 && (wIndex < pIndex || pIndex <= 0)) { 901 subStr = subStr.substring(0, wIndex); 902 } else if (pIndex > 0) { 903 subStr = subStr.substring(0, pIndex); 904 } 905 } 906 return subStr; 907 } 908 909 //CDMA 910 @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) updateParent(GsmCdmaCall oldParent, GsmCdmaCall newParent)911 public void updateParent(GsmCdmaCall oldParent, GsmCdmaCall newParent){ 912 if (newParent != oldParent) { 913 if (oldParent != null) { 914 oldParent.detach(this); 915 } 916 newParent.attachFake(this, GsmCdmaCall.State.ACTIVE); 917 mParent = newParent; 918 } 919 } 920 921 @Override finalize()922 protected void finalize() 923 { 924 /** 925 * It is understood that This finalizer is not guaranteed 926 * to be called and the release lock call is here just in 927 * case there is some path that doesn't call onDisconnect 928 * and or onConnectedInOrOut. 929 */ 930 if (mPartialWakeLock != null && mPartialWakeLock.isHeld()) { 931 Rlog.e(LOG_TAG, "UNEXPECTED; mPartialWakeLock is held when finalizing."); 932 } 933 clearPostDialListeners(); 934 releaseWakeLock(); 935 } 936 937 private void processNextPostDialChar()938 processNextPostDialChar() { 939 char c = 0; 940 Registrant postDialHandler; 941 942 if (mPostDialState == PostDialState.CANCELLED) { 943 releaseWakeLock(); 944 return; 945 } 946 947 if (mPostDialString == null || 948 mPostDialString.length() <= mNextPostDialChar) { 949 setPostDialState(PostDialState.COMPLETE); 950 951 // We were holding a wake lock until pause-dial was complete, so give it up now 952 releaseWakeLock(); 953 954 // notifyMessage.arg1 is 0 on complete 955 c = 0; 956 } else { 957 boolean isValid; 958 959 setPostDialState(PostDialState.STARTED); 960 961 c = mPostDialString.charAt(mNextPostDialChar++); 962 963 isValid = processPostDialChar(c); 964 965 if (!isValid) { 966 // Will call processNextPostDialChar 967 mHandler.obtainMessage(EVENT_NEXT_POST_DIAL).sendToTarget(); 968 // Don't notify application 969 Rlog.e(LOG_TAG, "processNextPostDialChar: c=" + c + " isn't valid!"); 970 return; 971 } 972 } 973 974 notifyPostDialListenersNextChar(c); 975 976 // TODO: remove the following code since the handler no longer executes anything. 977 postDialHandler = mOwner.getPhone().getPostDialHandler(); 978 979 Message notifyMessage; 980 981 if (postDialHandler != null 982 && (notifyMessage = postDialHandler.messageForRegistrant()) != null) { 983 // The AsyncResult.result is the Connection object 984 PostDialState state = mPostDialState; 985 AsyncResult ar = AsyncResult.forMessage(notifyMessage); 986 ar.result = this; 987 ar.userObj = state; 988 989 // arg1 is the character that was/is being processed 990 notifyMessage.arg1 = c; 991 992 //Rlog.v("GsmCdma", "##### processNextPostDialChar: send msg to postDialHandler, arg1=" + c); 993 notifyMessage.sendToTarget(); 994 } 995 } 996 997 /** "connecting" means "has never been ACTIVE" for both incoming 998 * and outgoing calls 999 */ 1000 private boolean isConnectingInOrOut()1001 isConnectingInOrOut() { 1002 return mParent == null || mParent == mOwner.mRingingCall 1003 || mParent.mState == GsmCdmaCall.State.DIALING 1004 || mParent.mState == GsmCdmaCall.State.ALERTING; 1005 } 1006 1007 private GsmCdmaCall parentFromDCState(DriverCall.State state)1008 parentFromDCState (DriverCall.State state) { 1009 switch (state) { 1010 case ACTIVE: 1011 case DIALING: 1012 case ALERTING: 1013 return mOwner.mForegroundCall; 1014 //break; 1015 1016 case HOLDING: 1017 return mOwner.mBackgroundCall; 1018 //break; 1019 1020 case INCOMING: 1021 case WAITING: 1022 return mOwner.mRingingCall; 1023 //break; 1024 1025 default: 1026 throw new RuntimeException("illegal call state: " + state); 1027 } 1028 } 1029 getAudioQualityFromDC(int audioQuality)1030 private int getAudioQualityFromDC(int audioQuality) { 1031 switch (audioQuality) { 1032 case DriverCall.AUDIO_QUALITY_AMR_WB: 1033 case DriverCall.AUDIO_QUALITY_EVRC_NW: 1034 return Connection.AUDIO_QUALITY_HIGH_DEFINITION; 1035 default: 1036 return Connection.AUDIO_QUALITY_STANDARD; 1037 } 1038 } 1039 1040 /** 1041 * Set post dial state and acquire wake lock while switching to "started" or "pause" 1042 * state, the wake lock will be released if state switches out of "started" or "pause" 1043 * state or after WAKE_LOCK_TIMEOUT_MILLIS. 1044 * @param s new PostDialState 1045 */ setPostDialState(PostDialState s)1046 private void setPostDialState(PostDialState s) { 1047 if (s == PostDialState.STARTED 1048 || s == PostDialState.PAUSE) { 1049 synchronized (mPartialWakeLock) { 1050 if (mPartialWakeLock.isHeld()) { 1051 mHandler.removeMessages(EVENT_WAKE_LOCK_TIMEOUT); 1052 } else { 1053 acquireWakeLock(); 1054 } 1055 Message msg = mHandler.obtainMessage(EVENT_WAKE_LOCK_TIMEOUT); 1056 mHandler.sendMessageDelayed(msg, WAKE_LOCK_TIMEOUT_MILLIS); 1057 } 1058 } else { 1059 mHandler.removeMessages(EVENT_WAKE_LOCK_TIMEOUT); 1060 releaseWakeLock(); 1061 } 1062 mPostDialState = s; 1063 notifyPostDialListeners(); 1064 } 1065 1066 @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) createWakeLock(Context context)1067 private void createWakeLock(Context context) { 1068 PowerManager pm = (PowerManager) context.getSystemService(Context.POWER_SERVICE); 1069 mPartialWakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, LOG_TAG); 1070 } 1071 1072 @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) acquireWakeLock()1073 private void acquireWakeLock() { 1074 if (mPartialWakeLock != null) { 1075 synchronized (mPartialWakeLock) { 1076 log("acquireWakeLock"); 1077 mPartialWakeLock.acquire(); 1078 } 1079 } 1080 } 1081 releaseWakeLock()1082 private void releaseWakeLock() { 1083 if (mPartialWakeLock != null) { 1084 synchronized (mPartialWakeLock) { 1085 if (mPartialWakeLock.isHeld()) { 1086 log("releaseWakeLock"); 1087 mPartialWakeLock.release(); 1088 } 1089 } 1090 } 1091 } 1092 releaseAllWakeLocks()1093 private void releaseAllWakeLocks() { 1094 if (mPartialWakeLock != null) { 1095 synchronized (mPartialWakeLock) { 1096 while (mPartialWakeLock.isHeld()) { 1097 mPartialWakeLock.release(); 1098 } 1099 } 1100 } 1101 } 1102 1103 @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) isPause(char c)1104 private static boolean isPause(char c) { 1105 return c == PhoneNumberUtils.PAUSE; 1106 } 1107 1108 @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) isWait(char c)1109 private static boolean isWait(char c) { 1110 return c == PhoneNumberUtils.WAIT; 1111 } 1112 isWild(char c)1113 private static boolean isWild(char c) { 1114 return c == PhoneNumberUtils.WILD; 1115 } 1116 1117 //CDMA 1118 // This function is to find the next PAUSE character index if 1119 // multiple pauses in a row. Otherwise it finds the next non PAUSE or 1120 // non WAIT character index. 1121 @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) findNextPCharOrNonPOrNonWCharIndex(String phoneNumber, int currIndex)1122 private static int findNextPCharOrNonPOrNonWCharIndex(String phoneNumber, int currIndex) { 1123 boolean wMatched = isWait(phoneNumber.charAt(currIndex)); 1124 int index = currIndex + 1; 1125 int length = phoneNumber.length(); 1126 while (index < length) { 1127 char cNext = phoneNumber.charAt(index); 1128 // if there is any W inside P/W sequence,mark it 1129 if (isWait(cNext)) { 1130 wMatched = true; 1131 } 1132 // if any characters other than P/W chars after P/W sequence 1133 // we break out the loop and append the correct 1134 if (!isWait(cNext) && !isPause(cNext)) { 1135 break; 1136 } 1137 index++; 1138 } 1139 1140 // It means the PAUSE character(s) is in the middle of dial string 1141 // and it needs to be handled one by one. 1142 if ((index < length) && (index > (currIndex + 1)) && 1143 ((wMatched == false) && isPause(phoneNumber.charAt(currIndex)))) { 1144 return (currIndex + 1); 1145 } 1146 return index; 1147 } 1148 1149 // CDMA 1150 // This function returns either PAUSE or WAIT character to append. 1151 // It is based on the next non PAUSE/WAIT character in the phoneNumber and the 1152 // index for the current PAUSE/WAIT character 1153 @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) findPOrWCharToAppend(String phoneNumber, int currPwIndex, int nextNonPwCharIndex)1154 private static char findPOrWCharToAppend(String phoneNumber, int currPwIndex, 1155 int nextNonPwCharIndex) { 1156 char c = phoneNumber.charAt(currPwIndex); 1157 char ret; 1158 1159 // Append the PW char 1160 ret = (isPause(c)) ? PhoneNumberUtils.PAUSE : PhoneNumberUtils.WAIT; 1161 1162 // If the nextNonPwCharIndex is greater than currPwIndex + 1, 1163 // it means the PW sequence contains not only P characters. 1164 // Since for the sequence that only contains P character, 1165 // the P character is handled one by one, the nextNonPwCharIndex 1166 // equals to currPwIndex + 1. 1167 // In this case, skip P, append W. 1168 if (nextNonPwCharIndex > (currPwIndex + 1)) { 1169 ret = PhoneNumberUtils.WAIT; 1170 } 1171 return ret; 1172 } 1173 1174 @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) maskDialString(String dialString)1175 private String maskDialString(String dialString) { 1176 if (VDBG) { 1177 return dialString; 1178 } 1179 1180 return "<MASKED>"; 1181 } 1182 1183 @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) fetchDtmfToneDelay(GsmCdmaPhone phone)1184 private void fetchDtmfToneDelay(GsmCdmaPhone phone) { 1185 CarrierConfigManager configMgr = (CarrierConfigManager) 1186 phone.getContext().getSystemService(Context.CARRIER_CONFIG_SERVICE); 1187 PersistableBundle b = configMgr.getConfigForSubId(phone.getSubId()); 1188 if (b != null) { 1189 mDtmfToneDelay = b.getInt(phone.getDtmfToneDelayKey()); 1190 } 1191 } 1192 1193 @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) isPhoneTypeGsm()1194 private boolean isPhoneTypeGsm() { 1195 return mOwner.getPhone().getPhoneType() == PhoneConstants.PHONE_TYPE_GSM; 1196 } 1197 1198 @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) log(String msg)1199 private void log(String msg) { 1200 Rlog.d(LOG_TAG, "[GsmCdmaConn] " + msg); 1201 } 1202 1203 @Override getNumberPresentation()1204 public int getNumberPresentation() { 1205 return mNumberPresentation; 1206 } 1207 1208 @Override getUUSInfo()1209 public UUSInfo getUUSInfo() { 1210 return mUusInfo; 1211 } 1212 getPreciseDisconnectCause()1213 public int getPreciseDisconnectCause() { 1214 return mPreciseCause; 1215 } 1216 1217 @Override getVendorDisconnectCause()1218 public String getVendorDisconnectCause() { 1219 return mVendorCause; 1220 } 1221 1222 @Override migrateFrom(Connection c)1223 public void migrateFrom(Connection c) { 1224 if (c == null) return; 1225 1226 super.migrateFrom(c); 1227 1228 this.mUusInfo = c.getUUSInfo(); 1229 1230 this.setUserData(c.getUserData()); 1231 } 1232 1233 @Override getOrigConnection()1234 public Connection getOrigConnection() { 1235 return mOrigConnection; 1236 } 1237 1238 @Override isMultiparty()1239 public boolean isMultiparty() { 1240 if (mOrigConnection != null) { 1241 return mOrigConnection.isMultiparty(); 1242 } 1243 1244 return false; 1245 } 1246 1247 /** 1248 * Get the corresponding EmergencyNumberTracker associated with the connection. 1249 * @return the EmergencyNumberTracker 1250 */ getEmergencyNumberTracker()1251 public EmergencyNumberTracker getEmergencyNumberTracker() { 1252 if (mOwner != null) { 1253 Phone phone = mOwner.getPhone(); 1254 if (phone != null) { 1255 return phone.getEmergencyNumberTracker(); 1256 } 1257 } 1258 return null; 1259 } 1260 1261 /** 1262 * @return {@code true} if this call is an OTASP activation call, {@code false} otherwise. 1263 */ isOtaspCall()1264 public boolean isOtaspCall() { 1265 return mAddress != null && OTASP_NUMBER.equals(mAddress); 1266 } 1267 } 1268