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