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