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