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