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