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