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