GsmCdmaConnection.java revision 86ab6968a950b56a682da335ff73243f35cb8485
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            // Only release wake lock for incoming calls, for outgoing calls the wake lock
706            // will be released after any pause-dial is completed
707            releaseWakeLock();
708        }
709    }
710
711    private void
712    doDisconnect() {
713        mIndex = -1;
714        mDisconnectTime = System.currentTimeMillis();
715        mDuration = SystemClock.elapsedRealtime() - mConnectTimeReal;
716        mDisconnected = true;
717        clearPostDialListeners();
718    }
719
720    /*package*/ void
721    onStartedHolding() {
722        mHoldingStartTime = SystemClock.elapsedRealtime();
723    }
724
725    /**
726     * Performs the appropriate action for a post-dial char, but does not
727     * notify application. returns false if the character is invalid and
728     * should be ignored
729     */
730    private boolean
731    processPostDialChar(char c) {
732        if (PhoneNumberUtils.is12Key(c)) {
733            mOwner.mCi.sendDtmf(c, mHandler.obtainMessage(EVENT_DTMF_DONE));
734        } else if (isPause(c)) {
735            if (!isPhoneTypeGsm()) {
736                setPostDialState(PostDialState.PAUSE);
737            }
738            // From TS 22.101:
739            // It continues...
740            // Upon the called party answering the UE shall send the DTMF digits
741            // automatically to the network after a delay of 3 seconds( 20 ).
742            // The digits shall be sent according to the procedures and timing
743            // specified in 3GPP TS 24.008 [13]. The first occurrence of the
744            // "DTMF Control Digits Separator" shall be used by the ME to
745            // distinguish between the addressing digits (i.e. the phone number)
746            // and the DTMF digits. Upon subsequent occurrences of the
747            // separator,
748            // the UE shall pause again for 3 seconds ( 20 ) before sending
749            // any further DTMF digits.
750            mHandler.sendMessageDelayed(mHandler.obtainMessage(EVENT_PAUSE_DONE),
751                    isPhoneTypeGsm() ? PAUSE_DELAY_MILLIS_GSM: PAUSE_DELAY_MILLIS_CDMA);
752        } else if (isWait(c)) {
753            setPostDialState(PostDialState.WAIT);
754        } else if (isWild(c)) {
755            setPostDialState(PostDialState.WILD);
756        } else {
757            return false;
758        }
759
760        return true;
761    }
762
763    @Override
764    public String
765    getRemainingPostDialString() {
766        String subStr = super.getRemainingPostDialString();
767        if (!isPhoneTypeGsm() && !TextUtils.isEmpty(subStr)) {
768            int wIndex = subStr.indexOf(PhoneNumberUtils.WAIT);
769            int pIndex = subStr.indexOf(PhoneNumberUtils.PAUSE);
770
771            if (wIndex > 0 && (wIndex < pIndex || pIndex <= 0)) {
772                subStr = subStr.substring(0, wIndex);
773            } else if (pIndex > 0) {
774                subStr = subStr.substring(0, pIndex);
775            }
776        }
777        return subStr;
778    }
779
780    //CDMA
781    public void updateParent(GsmCdmaCall oldParent, GsmCdmaCall newParent){
782        if (newParent != oldParent) {
783            if (oldParent != null) {
784                oldParent.detach(this);
785            }
786            newParent.attachFake(this, GsmCdmaCall.State.ACTIVE);
787            mParent = newParent;
788        }
789    }
790
791    @Override
792    protected void finalize()
793    {
794        /**
795         * It is understood that This finializer is not guaranteed
796         * to be called and the release lock call is here just in
797         * case there is some path that doesn't call onDisconnect
798         * and or onConnectedInOrOut.
799         */
800        if (mPartialWakeLock.isHeld()) {
801            Rlog.e(LOG_TAG, "[GsmCdmaConn] UNEXPECTED; mPartialWakeLock is held when finalizing.");
802        }
803        clearPostDialListeners();
804        releaseWakeLock();
805    }
806
807    private void
808    processNextPostDialChar() {
809        char c = 0;
810        Registrant postDialHandler;
811
812        if (mPostDialState == PostDialState.CANCELLED) {
813            releaseWakeLock();
814            return;
815        }
816
817        if (mPostDialString == null ||
818                mPostDialString.length() <= mNextPostDialChar) {
819            setPostDialState(PostDialState.COMPLETE);
820
821            // We were holding a wake lock until pause-dial was complete, so give it up now
822            releaseWakeLock();
823
824            // notifyMessage.arg1 is 0 on complete
825            c = 0;
826        } else {
827            boolean isValid;
828
829            setPostDialState(PostDialState.STARTED);
830
831            c = mPostDialString.charAt(mNextPostDialChar++);
832
833            isValid = processPostDialChar(c);
834
835            if (!isValid) {
836                // Will call processNextPostDialChar
837                mHandler.obtainMessage(EVENT_NEXT_POST_DIAL).sendToTarget();
838                // Don't notify application
839                Rlog.e(LOG_TAG, "processNextPostDialChar: c=" + c + " isn't valid!");
840                return;
841            }
842        }
843
844        notifyPostDialListenersNextChar(c);
845
846        // TODO: remove the following code since the handler no longer executes anything.
847        postDialHandler = mOwner.getPhone().getPostDialHandler();
848
849        Message notifyMessage;
850
851        if (postDialHandler != null
852                && (notifyMessage = postDialHandler.messageForRegistrant()) != null) {
853            // The AsyncResult.result is the Connection object
854            PostDialState state = mPostDialState;
855            AsyncResult ar = AsyncResult.forMessage(notifyMessage);
856            ar.result = this;
857            ar.userObj = state;
858
859            // arg1 is the character that was/is being processed
860            notifyMessage.arg1 = c;
861
862            //Rlog.v("GsmCdma", "##### processNextPostDialChar: send msg to postDialHandler, arg1=" + c);
863            notifyMessage.sendToTarget();
864        }
865    }
866
867    /** "connecting" means "has never been ACTIVE" for both incoming
868     *  and outgoing calls
869     */
870    private boolean
871    isConnectingInOrOut() {
872        return mParent == null || mParent == mOwner.mRingingCall
873            || mParent.mState == GsmCdmaCall.State.DIALING
874            || mParent.mState == GsmCdmaCall.State.ALERTING;
875    }
876
877    private GsmCdmaCall
878    parentFromDCState (DriverCall.State state) {
879        switch (state) {
880            case ACTIVE:
881            case DIALING:
882            case ALERTING:
883                return mOwner.mForegroundCall;
884            //break;
885
886            case HOLDING:
887                return mOwner.mBackgroundCall;
888            //break;
889
890            case INCOMING:
891            case WAITING:
892                return mOwner.mRingingCall;
893            //break;
894
895            default:
896                throw new RuntimeException("illegal call state: " + state);
897        }
898    }
899
900    /**
901     * Set post dial state and acquire wake lock while switching to "started" or "pause"
902     * state, the wake lock will be released if state switches out of "started" or "pause"
903     * state or after WAKE_LOCK_TIMEOUT_MILLIS.
904     * @param s new PostDialState
905     */
906    private void setPostDialState(PostDialState s) {
907        if (s == PostDialState.STARTED ||
908                s == PostDialState.PAUSE) {
909            synchronized (mPartialWakeLock) {
910                if (mPartialWakeLock.isHeld()) {
911                    mHandler.removeMessages(EVENT_WAKE_LOCK_TIMEOUT);
912                } else {
913                    acquireWakeLock();
914                }
915                Message msg = mHandler.obtainMessage(EVENT_WAKE_LOCK_TIMEOUT);
916                mHandler.sendMessageDelayed(msg, WAKE_LOCK_TIMEOUT_MILLIS);
917            }
918        } else {
919            mHandler.removeMessages(EVENT_WAKE_LOCK_TIMEOUT);
920            releaseWakeLock();
921        }
922        mPostDialState = s;
923        notifyPostDialListeners();
924    }
925
926    private void
927    createWakeLock(Context context) {
928        PowerManager pm = (PowerManager) context.getSystemService(Context.POWER_SERVICE);
929        mPartialWakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, LOG_TAG);
930    }
931
932    private void
933    acquireWakeLock() {
934        log("acquireWakeLock");
935        mPartialWakeLock.acquire();
936    }
937
938    private void
939    releaseWakeLock() {
940        synchronized(mPartialWakeLock) {
941            if (mPartialWakeLock.isHeld()) {
942                log("releaseWakeLock");
943                mPartialWakeLock.release();
944            }
945        }
946    }
947
948    private void
949    releaseAllWakeLocks() {
950        synchronized(mPartialWakeLock) {
951            while (mPartialWakeLock.isHeld()) {
952                mPartialWakeLock.release();
953            }
954        }
955    }
956
957    private static boolean isPause(char c) {
958        return c == PhoneNumberUtils.PAUSE;
959    }
960
961    private static boolean isWait(char c) {
962        return c == PhoneNumberUtils.WAIT;
963    }
964
965    private static boolean isWild(char c) {
966        return c == PhoneNumberUtils.WILD;
967    }
968
969    //CDMA
970    // This function is to find the next PAUSE character index if
971    // multiple pauses in a row. Otherwise it finds the next non PAUSE or
972    // non WAIT character index.
973    private static int
974    findNextPCharOrNonPOrNonWCharIndex(String phoneNumber, int currIndex) {
975        boolean wMatched = isWait(phoneNumber.charAt(currIndex));
976        int index = currIndex + 1;
977        int length = phoneNumber.length();
978        while (index < length) {
979            char cNext = phoneNumber.charAt(index);
980            // if there is any W inside P/W sequence,mark it
981            if (isWait(cNext)) {
982                wMatched = true;
983            }
984            // if any characters other than P/W chars after P/W sequence
985            // we break out the loop and append the correct
986            if (!isWait(cNext) && !isPause(cNext)) {
987                break;
988            }
989            index++;
990        }
991
992        // It means the PAUSE character(s) is in the middle of dial string
993        // and it needs to be handled one by one.
994        if ((index < length) && (index > (currIndex + 1))  &&
995                ((wMatched == false) && isPause(phoneNumber.charAt(currIndex)))) {
996            return (currIndex + 1);
997        }
998        return index;
999    }
1000
1001    //CDMA
1002    // This function returns either PAUSE or WAIT character to append.
1003    // It is based on the next non PAUSE/WAIT character in the phoneNumber and the
1004    // index for the current PAUSE/WAIT character
1005    private static char
1006    findPOrWCharToAppend(String phoneNumber, int currPwIndex, int nextNonPwCharIndex) {
1007        char c = phoneNumber.charAt(currPwIndex);
1008        char ret;
1009
1010        // Append the PW char
1011        ret = (isPause(c)) ? PhoneNumberUtils.PAUSE : PhoneNumberUtils.WAIT;
1012
1013        // If the nextNonPwCharIndex is greater than currPwIndex + 1,
1014        // it means the PW sequence contains not only P characters.
1015        // Since for the sequence that only contains P character,
1016        // the P character is handled one by one, the nextNonPwCharIndex
1017        // equals to currPwIndex + 1.
1018        // In this case, skip P, append W.
1019        if (nextNonPwCharIndex > (currPwIndex + 1)) {
1020            ret = PhoneNumberUtils.WAIT;
1021        }
1022        return ret;
1023    }
1024
1025    private String maskDialString(String dialString) {
1026        if (VDBG) {
1027            return dialString;
1028        }
1029
1030        return "<MASKED>";
1031    }
1032
1033    private void fetchDtmfToneDelay(GsmCdmaPhone phone) {
1034        CarrierConfigManager configMgr = (CarrierConfigManager)
1035                phone.getContext().getSystemService(Context.CARRIER_CONFIG_SERVICE);
1036        PersistableBundle b = configMgr.getConfigForSubId(phone.getSubId());
1037        if (b != null) {
1038            mDtmfToneDelay = b.getInt(phone.getDtmfToneDelayKey());
1039        }
1040    }
1041
1042    private boolean isPhoneTypeGsm() {
1043        return mOwner.getPhone().getPhoneType() == PhoneConstants.PHONE_TYPE_GSM;
1044    }
1045
1046    private void log(String msg) {
1047        Rlog.d(LOG_TAG, "[GsmCdmaConn] " + msg);
1048    }
1049
1050    @Override
1051    public int getNumberPresentation() {
1052        return mNumberPresentation;
1053    }
1054
1055    @Override
1056    public UUSInfo getUUSInfo() {
1057        return mUusInfo;
1058    }
1059
1060    public int getPreciseDisconnectCause() {
1061        return mPreciseCause;
1062    }
1063
1064    @Override
1065    public String getVendorDisconnectCause() {
1066        return mVendorCause;
1067    }
1068
1069    @Override
1070    public void migrateFrom(Connection c) {
1071        if (c == null) return;
1072
1073        super.migrateFrom(c);
1074
1075        this.mUusInfo = c.getUUSInfo();
1076
1077        this.setUserData(c.getUserData());
1078    }
1079
1080    @Override
1081    public Connection getOrigConnection() {
1082        return mOrigConnection;
1083    }
1084
1085    @Override
1086    public boolean isMultiparty() {
1087        if (mOrigConnection != null) {
1088            return mOrigConnection.isMultiparty();
1089        }
1090
1091        return false;
1092    }
1093}
1094