Home | History | Annotate | Download | only in cdma
      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 
     17 package com.android.internal.telephony.cdma;
     18 
     19 import com.android.internal.telephony.*;
     20 import android.content.Context;
     21 import android.os.AsyncResult;
     22 import android.os.Handler;
     23 import android.os.Looper;
     24 import android.os.Message;
     25 import android.os.PowerManager;
     26 import android.os.Registrant;
     27 import android.os.SystemClock;
     28 import android.os.SystemProperties;
     29 import android.util.Config;
     30 import android.util.Log;
     31 import android.text.TextUtils;
     32 
     33 import android.telephony.PhoneNumberUtils;
     34 import android.telephony.ServiceState;
     35 import com.android.internal.telephony.TelephonyProperties;
     36 
     37 /**
     38  * {@hide}
     39  */
     40 public 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     @Override
    950     public UUSInfo getUUSInfo() {
    951         // UUS information not supported in CDMA
    952         return null;
    953     }
    954 }
    955