GsmCdmaConnection.java revision 6bfad8034f8262da897f454fbe2ecc9f042a6090
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.CALL_BARRED:
443                return DisconnectCause.CALL_BARRED;
444
445            case CallFailCause.FDN_BLOCKED:
446                return DisconnectCause.FDN_BLOCKED;
447
448            case CallFailCause.IMEI_NOT_ACCEPTED:
449                return DisconnectCause.IMEI_NOT_ACCEPTED;
450
451            case CallFailCause.UNOBTAINABLE_NUMBER:
452                return DisconnectCause.UNOBTAINABLE_NUMBER;
453
454            case CallFailCause.DIAL_MODIFIED_TO_USSD:
455                return DisconnectCause.DIAL_MODIFIED_TO_USSD;
456
457            case CallFailCause.DIAL_MODIFIED_TO_SS:
458                return DisconnectCause.DIAL_MODIFIED_TO_SS;
459
460            case CallFailCause.DIAL_MODIFIED_TO_DIAL:
461                return DisconnectCause.DIAL_MODIFIED_TO_DIAL;
462
463            case CallFailCause.CDMA_LOCKED_UNTIL_POWER_CYCLE:
464                return DisconnectCause.CDMA_LOCKED_UNTIL_POWER_CYCLE;
465
466            case CallFailCause.CDMA_DROP:
467                return DisconnectCause.CDMA_DROP;
468
469            case CallFailCause.CDMA_INTERCEPT:
470                return DisconnectCause.CDMA_INTERCEPT;
471
472            case CallFailCause.CDMA_REORDER:
473                return DisconnectCause.CDMA_REORDER;
474
475            case CallFailCause.CDMA_SO_REJECT:
476                return DisconnectCause.CDMA_SO_REJECT;
477
478            case CallFailCause.CDMA_RETRY_ORDER:
479                return DisconnectCause.CDMA_RETRY_ORDER;
480
481            case CallFailCause.CDMA_ACCESS_FAILURE:
482                return DisconnectCause.CDMA_ACCESS_FAILURE;
483
484            case CallFailCause.CDMA_PREEMPTED:
485                return DisconnectCause.CDMA_PREEMPTED;
486
487            case CallFailCause.CDMA_NOT_EMERGENCY:
488                return DisconnectCause.CDMA_NOT_EMERGENCY;
489
490            case CallFailCause.CDMA_ACCESS_BLOCKED:
491                return DisconnectCause.CDMA_ACCESS_BLOCKED;
492
493            case CallFailCause.ERROR_UNSPECIFIED:
494            case CallFailCause.NORMAL_CLEARING:
495            default:
496                GsmCdmaPhone phone = mOwner.getPhone();
497                int serviceState = phone.getServiceState().getState();
498                UiccCardApplication cardApp = phone.getUiccCardApplication();
499                AppState uiccAppState = (cardApp != null) ? cardApp.getState() :
500                        AppState.APPSTATE_UNKNOWN;
501                if (serviceState == ServiceState.STATE_POWER_OFF) {
502                    return DisconnectCause.POWER_OFF;
503                }
504                if (!mIsEmergencyCall) {
505                    // Only send OUT_OF_SERVICE if it is not an emergency call. We can still
506                    // technically be in STATE_OUT_OF_SERVICE or STATE_EMERGENCY_ONLY during
507                    // an emergency call and when it ends, we do not want to mistakenly generate
508                    // an OUT_OF_SERVICE disconnect cause during normal call ending.
509                    if ((serviceState == ServiceState.STATE_OUT_OF_SERVICE
510                            || serviceState == ServiceState.STATE_EMERGENCY_ONLY)) {
511                        return DisconnectCause.OUT_OF_SERVICE;
512                    }
513                    // If we are placing an emergency call and the SIM is currently PIN/PUK
514                    // locked the AppState will always not be equal to APPSTATE_READY.
515                    if (uiccAppState != AppState.APPSTATE_READY) {
516                        if (isPhoneTypeGsm()) {
517                            return DisconnectCause.ICC_ERROR;
518                        } else { // CDMA
519                            if (phone.mCdmaSubscriptionSource ==
520                                    CdmaSubscriptionSourceManager.SUBSCRIPTION_FROM_RUIM) {
521                                return DisconnectCause.ICC_ERROR;
522                            }
523                        }
524                    }
525                }
526                if (isPhoneTypeGsm()) {
527                    if (causeCode == CallFailCause.ERROR_UNSPECIFIED) {
528                        if (phone.mSST.mRestrictedState.isCsRestricted()) {
529                            return DisconnectCause.CS_RESTRICTED;
530                        } else if (phone.mSST.mRestrictedState.isCsEmergencyRestricted()) {
531                            return DisconnectCause.CS_RESTRICTED_EMERGENCY;
532                        } else if (phone.mSST.mRestrictedState.isCsNormalRestricted()) {
533                            return DisconnectCause.CS_RESTRICTED_NORMAL;
534                        }
535                    }
536                }
537                if (causeCode == CallFailCause.NORMAL_CLEARING) {
538                    return DisconnectCause.NORMAL;
539                }
540                // If nothing else matches, report unknown call drop reason
541                // to app, not NORMAL call end.
542                return DisconnectCause.ERROR_UNSPECIFIED;
543        }
544    }
545
546    /*package*/ void
547    onRemoteDisconnect(int causeCode, String vendorCause) {
548        this.mPreciseCause = causeCode;
549        this.mVendorCause = vendorCause;
550        onDisconnect(disconnectCauseFromCode(causeCode));
551    }
552
553    /**
554     * Called when the radio indicates the connection has been disconnected.
555     * @param cause call disconnect cause; values are defined in {@link DisconnectCause}
556     */
557    @Override
558    public boolean onDisconnect(int cause) {
559        boolean changed = false;
560
561        mCause = cause;
562
563        if (!mDisconnected) {
564            doDisconnect();
565
566            if (DBG) Rlog.d(LOG_TAG, "onDisconnect: cause=" + cause);
567
568            mOwner.getPhone().notifyDisconnect(this);
569
570            if (mParent != null) {
571                changed = mParent.connectionDisconnected(this);
572            }
573
574            mOrigConnection = null;
575        }
576        clearPostDialListeners();
577        releaseWakeLock();
578        return changed;
579    }
580
581    //CDMA
582    /** Called when the call waiting connection has been hung up */
583    /*package*/ void
584    onLocalDisconnect() {
585        if (!mDisconnected) {
586            doDisconnect();
587            if (VDBG) Rlog.d(LOG_TAG, "onLoalDisconnect" );
588
589            if (mParent != null) {
590                mParent.detach(this);
591            }
592        }
593        releaseWakeLock();
594    }
595
596    // Returns true if state has changed, false if nothing changed
597    public boolean
598    update (DriverCall dc) {
599        GsmCdmaCall newParent;
600        boolean changed = false;
601        boolean wasConnectingInOrOut = isConnectingInOrOut();
602        boolean wasHolding = (getState() == GsmCdmaCall.State.HOLDING);
603
604        newParent = parentFromDCState(dc.state);
605
606        if (Phone.DEBUG_PHONE) log("parent= " +mParent +", newParent= " + newParent);
607
608        //Ignore dc.number and dc.name in case of a handover connection
609        if (isPhoneTypeGsm() && mOrigConnection != null) {
610            if (Phone.DEBUG_PHONE) log("update: mOrigConnection is not null");
611        } else {
612            log(" mNumberConverted " + mNumberConverted);
613            if (!equalsHandlesNulls(mAddress, dc.number) && (!mNumberConverted
614                    || !equalsHandlesNulls(mConvertedNumber, dc.number))) {
615                if (Phone.DEBUG_PHONE) log("update: phone # changed!");
616                mAddress = dc.number;
617                changed = true;
618            }
619        }
620
621        // A null cnapName should be the same as ""
622        if (TextUtils.isEmpty(dc.name)) {
623            if (!TextUtils.isEmpty(mCnapName)) {
624                changed = true;
625                mCnapName = "";
626            }
627        } else if (!dc.name.equals(mCnapName)) {
628            changed = true;
629            mCnapName = dc.name;
630        }
631
632        if (Phone.DEBUG_PHONE) log("--dssds----"+mCnapName);
633        mCnapNamePresentation = dc.namePresentation;
634        mNumberPresentation = dc.numberPresentation;
635
636        if (newParent != mParent) {
637            if (mParent != null) {
638                mParent.detach(this);
639            }
640            newParent.attach(this, dc);
641            mParent = newParent;
642            changed = true;
643        } else {
644            boolean parentStateChange;
645            parentStateChange = mParent.update (this, dc);
646            changed = changed || parentStateChange;
647        }
648
649        /** Some state-transition events */
650
651        if (Phone.DEBUG_PHONE) log(
652                "update: parent=" + mParent +
653                ", hasNewParent=" + (newParent != mParent) +
654                ", wasConnectingInOrOut=" + wasConnectingInOrOut +
655                ", wasHolding=" + wasHolding +
656                ", isConnectingInOrOut=" + isConnectingInOrOut() +
657                ", changed=" + changed);
658
659
660        if (wasConnectingInOrOut && !isConnectingInOrOut()) {
661            onConnectedInOrOut();
662        }
663
664        if (changed && !wasHolding && (getState() == GsmCdmaCall.State.HOLDING)) {
665            // We've transitioned into HOLDING
666            onStartedHolding();
667        }
668
669        return changed;
670    }
671
672    /**
673     * Called when this Connection is in the foregroundCall
674     * when a dial is initiated.
675     * We know we're ACTIVE, and we know we're going to end up
676     * HOLDING in the backgroundCall
677     */
678    void
679    fakeHoldBeforeDial() {
680        if (mParent != null) {
681            mParent.detach(this);
682        }
683
684        mParent = mOwner.mBackgroundCall;
685        mParent.attachFake(this, GsmCdmaCall.State.HOLDING);
686
687        onStartedHolding();
688    }
689
690    /*package*/ int
691    getGsmCdmaIndex() throws CallStateException {
692        if (mIndex >= 0) {
693            return mIndex + 1;
694        } else {
695            throw new CallStateException ("GsmCdma index not yet assigned");
696        }
697    }
698
699    /**
700     * An incoming or outgoing call has connected
701     */
702    void
703    onConnectedInOrOut() {
704        mConnectTime = System.currentTimeMillis();
705        mConnectTimeReal = SystemClock.elapsedRealtime();
706        mDuration = 0;
707
708        // bug #678474: incoming call interpreted as missed call, even though
709        // it sounds like the user has picked up the call.
710        if (Phone.DEBUG_PHONE) {
711            log("onConnectedInOrOut: connectTime=" + mConnectTime);
712        }
713
714        if (!mIsIncoming) {
715            // outgoing calls only
716            processNextPostDialChar();
717        } else {
718            // Only release wake lock for incoming calls, for outgoing calls the wake lock
719            // will be released after any pause-dial is completed
720            releaseWakeLock();
721        }
722    }
723
724    private void
725    doDisconnect() {
726        mIndex = -1;
727        mDisconnectTime = System.currentTimeMillis();
728        mDuration = SystemClock.elapsedRealtime() - mConnectTimeReal;
729        mDisconnected = true;
730        clearPostDialListeners();
731    }
732
733    /*package*/ void
734    onStartedHolding() {
735        mHoldingStartTime = SystemClock.elapsedRealtime();
736    }
737
738    /**
739     * Performs the appropriate action for a post-dial char, but does not
740     * notify application. returns false if the character is invalid and
741     * should be ignored
742     */
743    private boolean
744    processPostDialChar(char c) {
745        if (PhoneNumberUtils.is12Key(c)) {
746            mOwner.mCi.sendDtmf(c, mHandler.obtainMessage(EVENT_DTMF_DONE));
747        } else if (isPause(c)) {
748            if (!isPhoneTypeGsm()) {
749                setPostDialState(PostDialState.PAUSE);
750            }
751            // From TS 22.101:
752            // It continues...
753            // Upon the called party answering the UE shall send the DTMF digits
754            // automatically to the network after a delay of 3 seconds( 20 ).
755            // The digits shall be sent according to the procedures and timing
756            // specified in 3GPP TS 24.008 [13]. The first occurrence of the
757            // "DTMF Control Digits Separator" shall be used by the ME to
758            // distinguish between the addressing digits (i.e. the phone number)
759            // and the DTMF digits. Upon subsequent occurrences of the
760            // separator,
761            // the UE shall pause again for 3 seconds ( 20 ) before sending
762            // any further DTMF digits.
763            mHandler.sendMessageDelayed(mHandler.obtainMessage(EVENT_PAUSE_DONE),
764                    isPhoneTypeGsm() ? PAUSE_DELAY_MILLIS_GSM: PAUSE_DELAY_MILLIS_CDMA);
765        } else if (isWait(c)) {
766            setPostDialState(PostDialState.WAIT);
767        } else if (isWild(c)) {
768            setPostDialState(PostDialState.WILD);
769        } else {
770            return false;
771        }
772
773        return true;
774    }
775
776    @Override
777    public String
778    getRemainingPostDialString() {
779        String subStr = super.getRemainingPostDialString();
780        if (!isPhoneTypeGsm() && !TextUtils.isEmpty(subStr)) {
781            int wIndex = subStr.indexOf(PhoneNumberUtils.WAIT);
782            int pIndex = subStr.indexOf(PhoneNumberUtils.PAUSE);
783
784            if (wIndex > 0 && (wIndex < pIndex || pIndex <= 0)) {
785                subStr = subStr.substring(0, wIndex);
786            } else if (pIndex > 0) {
787                subStr = subStr.substring(0, pIndex);
788            }
789        }
790        return subStr;
791    }
792
793    //CDMA
794    public void updateParent(GsmCdmaCall oldParent, GsmCdmaCall newParent){
795        if (newParent != oldParent) {
796            if (oldParent != null) {
797                oldParent.detach(this);
798            }
799            newParent.attachFake(this, GsmCdmaCall.State.ACTIVE);
800            mParent = newParent;
801        }
802    }
803
804    @Override
805    protected void finalize()
806    {
807        /**
808         * It is understood that This finalizer is not guaranteed
809         * to be called and the release lock call is here just in
810         * case there is some path that doesn't call onDisconnect
811         * and or onConnectedInOrOut.
812         */
813        if (mPartialWakeLock != null && mPartialWakeLock.isHeld()) {
814            Rlog.e(LOG_TAG, "UNEXPECTED; mPartialWakeLock is held when finalizing.");
815        }
816        clearPostDialListeners();
817        releaseWakeLock();
818    }
819
820    private void
821    processNextPostDialChar() {
822        char c = 0;
823        Registrant postDialHandler;
824
825        if (mPostDialState == PostDialState.CANCELLED) {
826            releaseWakeLock();
827            return;
828        }
829
830        if (mPostDialString == null ||
831                mPostDialString.length() <= mNextPostDialChar) {
832            setPostDialState(PostDialState.COMPLETE);
833
834            // We were holding a wake lock until pause-dial was complete, so give it up now
835            releaseWakeLock();
836
837            // notifyMessage.arg1 is 0 on complete
838            c = 0;
839        } else {
840            boolean isValid;
841
842            setPostDialState(PostDialState.STARTED);
843
844            c = mPostDialString.charAt(mNextPostDialChar++);
845
846            isValid = processPostDialChar(c);
847
848            if (!isValid) {
849                // Will call processNextPostDialChar
850                mHandler.obtainMessage(EVENT_NEXT_POST_DIAL).sendToTarget();
851                // Don't notify application
852                Rlog.e(LOG_TAG, "processNextPostDialChar: c=" + c + " isn't valid!");
853                return;
854            }
855        }
856
857        notifyPostDialListenersNextChar(c);
858
859        // TODO: remove the following code since the handler no longer executes anything.
860        postDialHandler = mOwner.getPhone().getPostDialHandler();
861
862        Message notifyMessage;
863
864        if (postDialHandler != null
865                && (notifyMessage = postDialHandler.messageForRegistrant()) != null) {
866            // The AsyncResult.result is the Connection object
867            PostDialState state = mPostDialState;
868            AsyncResult ar = AsyncResult.forMessage(notifyMessage);
869            ar.result = this;
870            ar.userObj = state;
871
872            // arg1 is the character that was/is being processed
873            notifyMessage.arg1 = c;
874
875            //Rlog.v("GsmCdma", "##### processNextPostDialChar: send msg to postDialHandler, arg1=" + c);
876            notifyMessage.sendToTarget();
877        }
878    }
879
880    /** "connecting" means "has never been ACTIVE" for both incoming
881     *  and outgoing calls
882     */
883    private boolean
884    isConnectingInOrOut() {
885        return mParent == null || mParent == mOwner.mRingingCall
886            || mParent.mState == GsmCdmaCall.State.DIALING
887            || mParent.mState == GsmCdmaCall.State.ALERTING;
888    }
889
890    private GsmCdmaCall
891    parentFromDCState (DriverCall.State state) {
892        switch (state) {
893            case ACTIVE:
894            case DIALING:
895            case ALERTING:
896                return mOwner.mForegroundCall;
897            //break;
898
899            case HOLDING:
900                return mOwner.mBackgroundCall;
901            //break;
902
903            case INCOMING:
904            case WAITING:
905                return mOwner.mRingingCall;
906            //break;
907
908            default:
909                throw new RuntimeException("illegal call state: " + state);
910        }
911    }
912
913    /**
914     * Set post dial state and acquire wake lock while switching to "started" or "pause"
915     * state, the wake lock will be released if state switches out of "started" or "pause"
916     * state or after WAKE_LOCK_TIMEOUT_MILLIS.
917     * @param s new PostDialState
918     */
919    private void setPostDialState(PostDialState s) {
920        if (s == PostDialState.STARTED ||
921                s == PostDialState.PAUSE) {
922            synchronized (mPartialWakeLock) {
923                if (mPartialWakeLock.isHeld()) {
924                    mHandler.removeMessages(EVENT_WAKE_LOCK_TIMEOUT);
925                } else {
926                    acquireWakeLock();
927                }
928                Message msg = mHandler.obtainMessage(EVENT_WAKE_LOCK_TIMEOUT);
929                mHandler.sendMessageDelayed(msg, WAKE_LOCK_TIMEOUT_MILLIS);
930            }
931        } else {
932            mHandler.removeMessages(EVENT_WAKE_LOCK_TIMEOUT);
933            releaseWakeLock();
934        }
935        mPostDialState = s;
936        notifyPostDialListeners();
937    }
938
939    private void createWakeLock(Context context) {
940        PowerManager pm = (PowerManager) context.getSystemService(Context.POWER_SERVICE);
941        mPartialWakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, LOG_TAG);
942    }
943
944    private void acquireWakeLock() {
945        if (mPartialWakeLock != null) {
946            synchronized (mPartialWakeLock) {
947                log("acquireWakeLock");
948                mPartialWakeLock.acquire();
949            }
950        }
951    }
952
953    private void releaseWakeLock() {
954        if (mPartialWakeLock != null) {
955            synchronized (mPartialWakeLock) {
956                if (mPartialWakeLock.isHeld()) {
957                    log("releaseWakeLock");
958                    mPartialWakeLock.release();
959                }
960            }
961        }
962    }
963
964    private void releaseAllWakeLocks() {
965        if (mPartialWakeLock != null) {
966            synchronized (mPartialWakeLock) {
967                while (mPartialWakeLock.isHeld()) {
968                    mPartialWakeLock.release();
969                }
970            }
971        }
972    }
973
974    private static boolean isPause(char c) {
975        return c == PhoneNumberUtils.PAUSE;
976    }
977
978    private static boolean isWait(char c) {
979        return c == PhoneNumberUtils.WAIT;
980    }
981
982    private static boolean isWild(char c) {
983        return c == PhoneNumberUtils.WILD;
984    }
985
986    //CDMA
987    // This function is to find the next PAUSE character index if
988    // multiple pauses in a row. Otherwise it finds the next non PAUSE or
989    // non WAIT character index.
990    private static int findNextPCharOrNonPOrNonWCharIndex(String phoneNumber, int currIndex) {
991        boolean wMatched = isWait(phoneNumber.charAt(currIndex));
992        int index = currIndex + 1;
993        int length = phoneNumber.length();
994        while (index < length) {
995            char cNext = phoneNumber.charAt(index);
996            // if there is any W inside P/W sequence,mark it
997            if (isWait(cNext)) {
998                wMatched = true;
999            }
1000            // if any characters other than P/W chars after P/W sequence
1001            // we break out the loop and append the correct
1002            if (!isWait(cNext) && !isPause(cNext)) {
1003                break;
1004            }
1005            index++;
1006        }
1007
1008        // It means the PAUSE character(s) is in the middle of dial string
1009        // and it needs to be handled one by one.
1010        if ((index < length) && (index > (currIndex + 1))  &&
1011                ((wMatched == false) && isPause(phoneNumber.charAt(currIndex)))) {
1012            return (currIndex + 1);
1013        }
1014        return index;
1015    }
1016
1017    // CDMA
1018    // This function returns either PAUSE or WAIT character to append.
1019    // It is based on the next non PAUSE/WAIT character in the phoneNumber and the
1020    // index for the current PAUSE/WAIT character
1021    private static char findPOrWCharToAppend(String phoneNumber, int currPwIndex,
1022                                             int nextNonPwCharIndex) {
1023        char c = phoneNumber.charAt(currPwIndex);
1024        char ret;
1025
1026        // Append the PW char
1027        ret = (isPause(c)) ? PhoneNumberUtils.PAUSE : PhoneNumberUtils.WAIT;
1028
1029        // If the nextNonPwCharIndex is greater than currPwIndex + 1,
1030        // it means the PW sequence contains not only P characters.
1031        // Since for the sequence that only contains P character,
1032        // the P character is handled one by one, the nextNonPwCharIndex
1033        // equals to currPwIndex + 1.
1034        // In this case, skip P, append W.
1035        if (nextNonPwCharIndex > (currPwIndex + 1)) {
1036            ret = PhoneNumberUtils.WAIT;
1037        }
1038        return ret;
1039    }
1040
1041    private String maskDialString(String dialString) {
1042        if (VDBG) {
1043            return dialString;
1044        }
1045
1046        return "<MASKED>";
1047    }
1048
1049    private void fetchDtmfToneDelay(GsmCdmaPhone phone) {
1050        CarrierConfigManager configMgr = (CarrierConfigManager)
1051                phone.getContext().getSystemService(Context.CARRIER_CONFIG_SERVICE);
1052        PersistableBundle b = configMgr.getConfigForSubId(phone.getSubId());
1053        if (b != null) {
1054            mDtmfToneDelay = b.getInt(phone.getDtmfToneDelayKey());
1055        }
1056    }
1057
1058    private boolean isPhoneTypeGsm() {
1059        return mOwner.getPhone().getPhoneType() == PhoneConstants.PHONE_TYPE_GSM;
1060    }
1061
1062    private void log(String msg) {
1063        Rlog.d(LOG_TAG, "[GsmCdmaConn] " + msg);
1064    }
1065
1066    @Override
1067    public int getNumberPresentation() {
1068        return mNumberPresentation;
1069    }
1070
1071    @Override
1072    public UUSInfo getUUSInfo() {
1073        return mUusInfo;
1074    }
1075
1076    public int getPreciseDisconnectCause() {
1077        return mPreciseCause;
1078    }
1079
1080    @Override
1081    public String getVendorDisconnectCause() {
1082        return mVendorCause;
1083    }
1084
1085    @Override
1086    public void migrateFrom(Connection c) {
1087        if (c == null) return;
1088
1089        super.migrateFrom(c);
1090
1091        this.mUusInfo = c.getUUSInfo();
1092
1093        this.setUserData(c.getUserData());
1094    }
1095
1096    @Override
1097    public Connection getOrigConnection() {
1098        return mOrigConnection;
1099    }
1100
1101    @Override
1102    public boolean isMultiparty() {
1103        if (mOrigConnection != null) {
1104            return mOrigConnection.isMultiparty();
1105        }
1106
1107        return false;
1108    }
1109}
1110