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