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