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