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