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