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