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