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