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