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.test; 18 19 import android.os.Looper; 20 import android.os.Message; 21 import android.os.Handler; 22 import android.telephony.PhoneNumberUtils; 23 import com.android.internal.telephony.ATParseEx; 24 import com.android.internal.telephony.DriverCall; 25 import java.util.List; 26 import java.util.ArrayList; 27 28 import android.telephony.Rlog; 29 30 class CallInfo { 31 enum State { 32 ACTIVE(0), 33 HOLDING(1), 34 DIALING(2), // MO call only 35 ALERTING(3), // MO call only 36 INCOMING(4), // MT call only 37 WAITING(5); // MT call only 38 State(int value)39 State(int value) {mValue = value;} 40 41 private final int mValue; value()42 public int value() {return mValue;} 43 } 44 45 boolean mIsMT; 46 State mState; 47 boolean mIsMpty; 48 String mNumber; 49 int mTOA; 50 CallInfo(boolean isMT, State state, boolean isMpty, String number)51 CallInfo (boolean isMT, State state, boolean isMpty, String number) { 52 mIsMT = isMT; 53 mState = state; 54 mIsMpty = isMpty; 55 mNumber = number; 56 57 if (number.length() > 0 && number.charAt(0) == '+') { 58 mTOA = PhoneNumberUtils.TOA_International; 59 } else { 60 mTOA = PhoneNumberUtils.TOA_Unknown; 61 } 62 } 63 64 static CallInfo createOutgoingCall(String number)65 createOutgoingCall(String number) { 66 return new CallInfo (false, State.DIALING, false, number); 67 } 68 69 static CallInfo createIncomingCall(String number)70 createIncomingCall(String number) { 71 return new CallInfo (true, State.INCOMING, false, number); 72 } 73 74 String toCLCCLine(int index)75 toCLCCLine(int index) { 76 return 77 "+CLCC: " 78 + index + "," + (mIsMT ? "1" : "0") +"," 79 + mState.value() + ",0," + (mIsMpty ? "1" : "0") 80 + ",\"" + mNumber + "\"," + mTOA; 81 } 82 83 DriverCall toDriverCall(int index)84 toDriverCall(int index) { 85 DriverCall ret; 86 87 ret = new DriverCall(); 88 89 ret.index = index; 90 ret.isMT = mIsMT; 91 92 try { 93 ret.state = DriverCall.stateFromCLCC(mState.value()); 94 } catch (ATParseEx ex) { 95 throw new RuntimeException("should never happen", ex); 96 } 97 98 ret.isMpty = mIsMpty; 99 ret.number = mNumber; 100 ret.TOA = mTOA; 101 ret.isVoice = true; 102 ret.als = 0; 103 104 return ret; 105 } 106 107 108 boolean isActiveOrHeld()109 isActiveOrHeld() { 110 return mState == State.ACTIVE || mState == State.HOLDING; 111 } 112 113 boolean isConnecting()114 isConnecting() { 115 return mState == State.DIALING || mState == State.ALERTING; 116 } 117 118 boolean isRinging()119 isRinging() { 120 return mState == State.INCOMING || mState == State.WAITING; 121 } 122 123 } 124 125 class InvalidStateEx extends Exception { InvalidStateEx()126 InvalidStateEx() { 127 128 } 129 } 130 131 132 class SimulatedGsmCallState extends Handler { 133 //***** Instance Variables 134 135 CallInfo mCalls[] = new CallInfo[MAX_CALLS]; 136 137 private boolean mAutoProgressConnecting = true; 138 private boolean mNextDialFailImmediately; 139 140 141 //***** Event Constants 142 143 static final int EVENT_PROGRESS_CALL_STATE = 1; 144 145 //***** Constants 146 147 static final int MAX_CALLS = 7; 148 /** number of msec between dialing -> alerting and alerting->active */ 149 static final int CONNECTING_PAUSE_MSEC = 5 * 100; 150 151 152 //***** Overridden from Handler 153 SimulatedGsmCallState(Looper looper)154 public SimulatedGsmCallState(Looper looper) { 155 super(looper); 156 } 157 158 @Override 159 public void handleMessage(Message msg)160 handleMessage(Message msg) { 161 synchronized(this) { switch (msg.what) { 162 // PLEASE REMEMBER 163 // calls may have hung up by the time delayed events happen 164 165 case EVENT_PROGRESS_CALL_STATE: 166 progressConnectingCallState(); 167 break; 168 }} 169 } 170 171 //***** Public Methods 172 173 /** 174 * Start the simulated phone ringing 175 * true if succeeded, false if failed 176 */ 177 public boolean triggerRing(String number)178 triggerRing(String number) { 179 synchronized (this) { 180 int empty = -1; 181 boolean isCallWaiting = false; 182 183 // ensure there aren't already calls INCOMING or WAITING 184 for (int i = 0 ; i < mCalls.length ; i++) { 185 CallInfo call = mCalls[i]; 186 187 if (call == null && empty < 0) { 188 empty = i; 189 } else if (call != null 190 && (call.mState == CallInfo.State.INCOMING 191 || call.mState == CallInfo.State.WAITING) 192 ) { 193 Rlog.w("ModelInterpreter", 194 "triggerRing failed; phone already ringing"); 195 return false; 196 } else if (call != null) { 197 isCallWaiting = true; 198 } 199 } 200 201 if (empty < 0 ) { 202 Rlog.w("ModelInterpreter", "triggerRing failed; all full"); 203 return false; 204 } 205 206 mCalls[empty] = CallInfo.createIncomingCall( 207 PhoneNumberUtils.extractNetworkPortion(number)); 208 209 if (isCallWaiting) { 210 mCalls[empty].mState = CallInfo.State.WAITING; 211 } 212 213 } 214 return true; 215 } 216 217 /** If a call is DIALING or ALERTING, progress it to the next state */ 218 public void progressConnectingCallState()219 progressConnectingCallState() { 220 synchronized (this) { 221 for (int i = 0 ; i < mCalls.length ; i++) { 222 CallInfo call = mCalls[i]; 223 224 if (call != null && call.mState == CallInfo.State.DIALING) { 225 call.mState = CallInfo.State.ALERTING; 226 227 if (mAutoProgressConnecting) { 228 sendMessageDelayed( 229 obtainMessage(EVENT_PROGRESS_CALL_STATE, call), 230 CONNECTING_PAUSE_MSEC); 231 } 232 break; 233 } else if (call != null 234 && call.mState == CallInfo.State.ALERTING 235 ) { 236 call.mState = CallInfo.State.ACTIVE; 237 break; 238 } 239 } 240 } 241 } 242 243 /** If a call is DIALING or ALERTING, progress it all the way to ACTIVE */ 244 public void progressConnectingToActive()245 progressConnectingToActive() { 246 synchronized (this) { 247 for (int i = 0 ; i < mCalls.length ; i++) { 248 CallInfo call = mCalls[i]; 249 250 if (call != null && (call.mState == CallInfo.State.DIALING 251 || call.mState == CallInfo.State.ALERTING) 252 ) { 253 call.mState = CallInfo.State.ACTIVE; 254 break; 255 } 256 } 257 } 258 } 259 260 /** automatically progress mobile originated calls to ACTIVE. 261 * default to true 262 */ 263 public void setAutoProgressConnectingCall(boolean b)264 setAutoProgressConnectingCall(boolean b) { 265 mAutoProgressConnecting = b; 266 } 267 268 public void setNextDialFailImmediately(boolean b)269 setNextDialFailImmediately(boolean b) { 270 mNextDialFailImmediately = b; 271 } 272 273 /** 274 * hangup ringing, dialing, or active calls 275 * returns true if call was hung up, false if not 276 */ 277 public boolean triggerHangupForeground()278 triggerHangupForeground() { 279 synchronized (this) { 280 boolean found; 281 282 found = false; 283 284 for (int i = 0 ; i < mCalls.length ; i++) { 285 CallInfo call = mCalls[i]; 286 287 if (call != null 288 && (call.mState == CallInfo.State.INCOMING 289 || call.mState == CallInfo.State.WAITING) 290 ) { 291 mCalls[i] = null; 292 found = true; 293 } 294 } 295 296 for (int i = 0 ; i < mCalls.length ; i++) { 297 CallInfo call = mCalls[i]; 298 299 if (call != null 300 && (call.mState == CallInfo.State.DIALING 301 || call.mState == CallInfo.State.ACTIVE 302 || call.mState == CallInfo.State.ALERTING) 303 ) { 304 mCalls[i] = null; 305 found = true; 306 } 307 } 308 return found; 309 } 310 } 311 312 /** 313 * hangup holding calls 314 * returns true if call was hung up, false if not 315 */ 316 public boolean triggerHangupBackground()317 triggerHangupBackground() { 318 synchronized (this) { 319 boolean found = false; 320 321 for (int i = 0 ; i < mCalls.length ; i++) { 322 CallInfo call = mCalls[i]; 323 324 if (call != null && call.mState == CallInfo.State.HOLDING) { 325 mCalls[i] = null; 326 found = true; 327 } 328 } 329 330 return found; 331 } 332 } 333 334 /** 335 * hangup all 336 * returns true if call was hung up, false if not 337 */ 338 public boolean triggerHangupAll()339 triggerHangupAll() { 340 synchronized(this) { 341 boolean found = false; 342 343 for (int i = 0 ; i < mCalls.length ; i++) { 344 CallInfo call = mCalls[i]; 345 346 if (mCalls[i] != null) { 347 found = true; 348 } 349 350 mCalls[i] = null; 351 } 352 353 return found; 354 } 355 } 356 357 public boolean onAnswer()358 onAnswer() { 359 synchronized (this) { 360 for (int i = 0 ; i < mCalls.length ; i++) { 361 CallInfo call = mCalls[i]; 362 363 if (call != null 364 && (call.mState == CallInfo.State.INCOMING 365 || call.mState == CallInfo.State.WAITING) 366 ) { 367 return switchActiveAndHeldOrWaiting(); 368 } 369 } 370 } 371 372 return false; 373 } 374 375 public boolean onHangup()376 onHangup() { 377 boolean found = false; 378 379 for (int i = 0 ; i < mCalls.length ; i++) { 380 CallInfo call = mCalls[i]; 381 382 if (call != null && call.mState != CallInfo.State.WAITING) { 383 mCalls[i] = null; 384 found = true; 385 } 386 } 387 388 return found; 389 } 390 391 public boolean onChld(char c0, char c1)392 onChld(char c0, char c1) { 393 boolean ret; 394 int callIndex = 0; 395 396 if (c1 != 0) { 397 callIndex = c1 - '1'; 398 399 if (callIndex < 0 || callIndex >= mCalls.length) { 400 return false; 401 } 402 } 403 404 switch (c0) { 405 case '0': 406 ret = releaseHeldOrUDUB(); 407 break; 408 case '1': 409 if (c1 <= 0) { 410 ret = releaseActiveAcceptHeldOrWaiting(); 411 } else { 412 if (mCalls[callIndex] == null) { 413 ret = false; 414 } else { 415 mCalls[callIndex] = null; 416 ret = true; 417 } 418 } 419 break; 420 case '2': 421 if (c1 <= 0) { 422 ret = switchActiveAndHeldOrWaiting(); 423 } else { 424 ret = separateCall(callIndex); 425 } 426 break; 427 case '3': 428 ret = conference(); 429 break; 430 case '4': 431 ret = explicitCallTransfer(); 432 break; 433 case '5': 434 if (true) { //just so javac doesnt complain about break 435 //CCBS not impled 436 ret = false; 437 } 438 break; 439 default: 440 ret = false; 441 442 } 443 444 return ret; 445 } 446 447 public boolean releaseHeldOrUDUB()448 releaseHeldOrUDUB() { 449 boolean found = false; 450 451 for (int i = 0 ; i < mCalls.length ; i++) { 452 CallInfo c = mCalls[i]; 453 454 if (c != null && c.isRinging()) { 455 found = true; 456 mCalls[i] = null; 457 break; 458 } 459 } 460 461 if (!found) { 462 for (int i = 0 ; i < mCalls.length ; i++) { 463 CallInfo c = mCalls[i]; 464 465 if (c != null && c.mState == CallInfo.State.HOLDING) { 466 found = true; 467 mCalls[i] = null; 468 // don't stop...there may be more than one 469 } 470 } 471 } 472 473 return true; 474 } 475 476 477 public boolean releaseActiveAcceptHeldOrWaiting()478 releaseActiveAcceptHeldOrWaiting() { 479 boolean foundHeld = false; 480 boolean foundActive = false; 481 482 for (int i = 0 ; i < mCalls.length ; i++) { 483 CallInfo c = mCalls[i]; 484 485 if (c != null && c.mState == CallInfo.State.ACTIVE) { 486 mCalls[i] = null; 487 foundActive = true; 488 } 489 } 490 491 if (!foundActive) { 492 // FIXME this may not actually be how most basebands react 493 // CHLD=1 may not hang up dialing/alerting calls 494 for (int i = 0 ; i < mCalls.length ; i++) { 495 CallInfo c = mCalls[i]; 496 497 if (c != null 498 && (c.mState == CallInfo.State.DIALING 499 || c.mState == CallInfo.State.ALERTING) 500 ) { 501 mCalls[i] = null; 502 foundActive = true; 503 } 504 } 505 } 506 507 for (int i = 0 ; i < mCalls.length ; i++) { 508 CallInfo c = mCalls[i]; 509 510 if (c != null && c.mState == CallInfo.State.HOLDING) { 511 c.mState = CallInfo.State.ACTIVE; 512 foundHeld = true; 513 } 514 } 515 516 if (foundHeld) { 517 return true; 518 } 519 520 for (int i = 0 ; i < mCalls.length ; i++) { 521 CallInfo c = mCalls[i]; 522 523 if (c != null && c.isRinging()) { 524 c.mState = CallInfo.State.ACTIVE; 525 return true; 526 } 527 } 528 529 return true; 530 } 531 532 public boolean switchActiveAndHeldOrWaiting()533 switchActiveAndHeldOrWaiting() { 534 boolean hasHeld = false; 535 536 // first, are there held calls? 537 for (int i = 0 ; i < mCalls.length ; i++) { 538 CallInfo c = mCalls[i]; 539 540 if (c != null && c.mState == CallInfo.State.HOLDING) { 541 hasHeld = true; 542 break; 543 } 544 } 545 546 // Now, switch 547 for (int i = 0 ; i < mCalls.length ; i++) { 548 CallInfo c = mCalls[i]; 549 550 if (c != null) { 551 if (c.mState == CallInfo.State.ACTIVE) { 552 c.mState = CallInfo.State.HOLDING; 553 } else if (c.mState == CallInfo.State.HOLDING) { 554 c.mState = CallInfo.State.ACTIVE; 555 } else if (!hasHeld && c.isRinging()) { 556 c.mState = CallInfo.State.ACTIVE; 557 } 558 } 559 } 560 561 return true; 562 } 563 564 565 public boolean separateCall(int index)566 separateCall(int index) { 567 try { 568 CallInfo c; 569 570 c = mCalls[index]; 571 572 if (c == null || c.isConnecting() || countActiveLines() != 1) { 573 return false; 574 } 575 576 c.mState = CallInfo.State.ACTIVE; 577 c.mIsMpty = false; 578 579 for (int i = 0 ; i < mCalls.length ; i++) { 580 int countHeld=0, lastHeld=0; 581 582 if (i != index) { 583 CallInfo cb = mCalls[i]; 584 585 if (cb != null && cb.mState == CallInfo.State.ACTIVE) { 586 cb.mState = CallInfo.State.HOLDING; 587 countHeld++; 588 lastHeld = i; 589 } 590 } 591 592 if (countHeld == 1) { 593 // if there's only one left, clear the MPTY flag 594 mCalls[lastHeld].mIsMpty = false; 595 } 596 } 597 598 return true; 599 } catch (InvalidStateEx ex) { 600 return false; 601 } 602 } 603 604 605 606 public boolean conference()607 conference() { 608 int countCalls = 0; 609 610 // if there's connecting calls, we can't do this yet 611 for (int i = 0 ; i < mCalls.length ; i++) { 612 CallInfo c = mCalls[i]; 613 614 if (c != null) { 615 countCalls++; 616 617 if (c.isConnecting()) { 618 return false; 619 } 620 } 621 } 622 for (int i = 0 ; i < mCalls.length ; i++) { 623 CallInfo c = mCalls[i]; 624 625 if (c != null) { 626 c.mState = CallInfo.State.ACTIVE; 627 if (countCalls > 0) { 628 c.mIsMpty = true; 629 } 630 } 631 } 632 633 return true; 634 } 635 636 public boolean explicitCallTransfer()637 explicitCallTransfer() { 638 int countCalls = 0; 639 640 // if there's connecting calls, we can't do this yet 641 for (int i = 0 ; i < mCalls.length ; i++) { 642 CallInfo c = mCalls[i]; 643 644 if (c != null) { 645 countCalls++; 646 647 if (c.isConnecting()) { 648 return false; 649 } 650 } 651 } 652 653 // disconnect the subscriber from both calls 654 return triggerHangupAll(); 655 } 656 657 public boolean onDial(String address)658 onDial(String address) { 659 CallInfo call; 660 int freeSlot = -1; 661 662 Rlog.d("GSM", "SC> dial '" + address + "'"); 663 664 if (mNextDialFailImmediately) { 665 mNextDialFailImmediately = false; 666 667 Rlog.d("GSM", "SC< dial fail (per request)"); 668 return false; 669 } 670 671 String phNum = PhoneNumberUtils.extractNetworkPortion(address); 672 673 if (phNum.length() == 0) { 674 Rlog.d("GSM", "SC< dial fail (invalid ph num)"); 675 return false; 676 } 677 678 // Ignore setting up GPRS 679 if (phNum.startsWith("*99") && phNum.endsWith("#")) { 680 Rlog.d("GSM", "SC< dial ignored (gprs)"); 681 return true; 682 } 683 684 // There can be at most 1 active "line" when we initiate 685 // a new call 686 try { 687 if (countActiveLines() > 1) { 688 Rlog.d("GSM", "SC< dial fail (invalid call state)"); 689 return false; 690 } 691 } catch (InvalidStateEx ex) { 692 Rlog.d("GSM", "SC< dial fail (invalid call state)"); 693 return false; 694 } 695 696 for (int i = 0 ; i < mCalls.length ; i++) { 697 if (freeSlot < 0 && mCalls[i] == null) { 698 freeSlot = i; 699 } 700 701 if (mCalls[i] != null && !mCalls[i].isActiveOrHeld()) { 702 // Can't make outgoing calls when there is a ringing or 703 // connecting outgoing call 704 Rlog.d("GSM", "SC< dial fail (invalid call state)"); 705 return false; 706 } else if (mCalls[i] != null && mCalls[i].mState == CallInfo.State.ACTIVE) { 707 // All active calls behome held 708 mCalls[i].mState = CallInfo.State.HOLDING; 709 } 710 } 711 712 if (freeSlot < 0) { 713 Rlog.d("GSM", "SC< dial fail (invalid call state)"); 714 return false; 715 } 716 717 mCalls[freeSlot] = CallInfo.createOutgoingCall(phNum); 718 719 if (mAutoProgressConnecting) { 720 sendMessageDelayed( 721 obtainMessage(EVENT_PROGRESS_CALL_STATE, mCalls[freeSlot]), 722 CONNECTING_PAUSE_MSEC); 723 } 724 725 Rlog.d("GSM", "SC< dial (slot = " + freeSlot + ")"); 726 727 return true; 728 } 729 730 public List<DriverCall> getDriverCalls()731 getDriverCalls() { 732 ArrayList<DriverCall> ret = new ArrayList<DriverCall>(mCalls.length); 733 734 for (int i = 0 ; i < mCalls.length ; i++) { 735 CallInfo c = mCalls[i]; 736 737 if (c != null) { 738 DriverCall dc; 739 740 dc = c.toDriverCall(i + 1); 741 ret.add(dc); 742 } 743 } 744 745 Rlog.d("GSM", "SC< getDriverCalls " + ret); 746 747 return ret; 748 } 749 750 public List<String> getClccLines()751 getClccLines() { 752 ArrayList<String> ret = new ArrayList<String>(mCalls.length); 753 754 for (int i = 0 ; i < mCalls.length ; i++) { 755 CallInfo c = mCalls[i]; 756 757 if (c != null) { 758 ret.add((c.toCLCCLine(i + 1))); 759 } 760 } 761 762 return ret; 763 } 764 765 private int countActiveLines()766 countActiveLines() throws InvalidStateEx { 767 boolean hasMpty = false; 768 boolean hasHeld = false; 769 boolean hasActive = false; 770 boolean hasConnecting = false; 771 boolean hasRinging = false; 772 boolean mptyIsHeld = false; 773 774 for (int i = 0 ; i < mCalls.length ; i++) { 775 CallInfo call = mCalls[i]; 776 777 if (call != null) { 778 if (!hasMpty && call.mIsMpty) { 779 mptyIsHeld = call.mState == CallInfo.State.HOLDING; 780 } else if (call.mIsMpty && mptyIsHeld 781 && call.mState == CallInfo.State.ACTIVE 782 ) { 783 Rlog.e("ModelInterpreter", "Invalid state"); 784 throw new InvalidStateEx(); 785 } else if (!call.mIsMpty && hasMpty && mptyIsHeld 786 && call.mState == CallInfo.State.HOLDING 787 ) { 788 Rlog.e("ModelInterpreter", "Invalid state"); 789 throw new InvalidStateEx(); 790 } 791 792 hasMpty |= call.mIsMpty; 793 hasHeld |= call.mState == CallInfo.State.HOLDING; 794 hasActive |= call.mState == CallInfo.State.ACTIVE; 795 hasConnecting |= call.isConnecting(); 796 hasRinging |= call.isRinging(); 797 } 798 } 799 800 int ret = 0; 801 802 if (hasHeld) ret++; 803 if (hasActive) ret++; 804 if (hasConnecting) ret++; 805 if (hasRinging) ret++; 806 807 return ret; 808 } 809 810 } 811