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 17package com.android.internal.telephony.test; 18 19import android.os.HandlerThread; 20import android.os.Looper; 21import android.os.Message; 22import android.util.Log; 23 24import java.io.IOException; 25import java.io.InputStream; 26import java.io.OutputStream; 27import java.io.UnsupportedEncodingException; 28import java.net.InetSocketAddress; 29import java.net.ServerSocket; 30import java.net.Socket; 31import java.util.List; 32 33// Also in ATChannel.java 34class LineReader 35{ 36 /** 37 * Not threadsafe 38 * Assumes input is ASCII 39 */ 40 41 //***** Constants 42 43 // For what it's worth, this is also the size of an 44 // OMAP CSMI mailbox 45 static final int BUFFER_SIZE = 0x1000; 46 47 // just to prevent constant allocations 48 byte buffer[] = new byte[BUFFER_SIZE]; 49 50 //***** Instance Variables 51 52 InputStream inStream; 53 54 LineReader (InputStream s) 55 { 56 inStream = s; 57 } 58 59 String 60 getNextLine() 61 { 62 return getNextLine(false); 63 } 64 65 String 66 getNextLineCtrlZ() 67 { 68 return getNextLine(true); 69 } 70 71 /** 72 * Note: doesn't return the last incomplete line read on EOF, since 73 * it doesn't typically matter anyway 74 * 75 * Returns NULL on EOF 76 */ 77 78 String 79 getNextLine(boolean ctrlZ) 80 { 81 int i = 0; 82 83 try { 84 for (;;) { 85 int result; 86 87 result = inStream.read(); 88 89 if (result < 0) { 90 return null; 91 } 92 93 if (ctrlZ && result == 0x1a) { 94 break; 95 } else if (result == '\r' || result == '\n') { 96 if (i == 0) { 97 // Skip leading cr/lf 98 continue; 99 } else { 100 break; 101 } 102 } 103 104 buffer[i++] = (byte)result; 105 } 106 } catch (IOException ex) { 107 return null; 108 } catch (IndexOutOfBoundsException ex) { 109 System.err.println("ATChannel: buffer overflow"); 110 } 111 112 try { 113 return new String(buffer, 0, i, "US-ASCII"); 114 } catch (UnsupportedEncodingException ex) { 115 System.err.println("ATChannel: implausable UnsupportedEncodingException"); 116 return null; 117 } 118 } 119} 120 121 122 123class InterpreterEx extends Exception 124{ 125 public 126 InterpreterEx (String result) 127 { 128 this.result = result; 129 } 130 131 String result; 132} 133 134public class ModelInterpreter 135 implements Runnable, SimulatedRadioControl 136{ 137 static final int MAX_CALLS = 6; 138 139 /** number of msec between dialing -> alerting and alerting->active */ 140 static final int CONNECTING_PAUSE_MSEC = 5 * 100; 141 142 static final String LOG_TAG = "ModelInterpreter"; 143 144 //***** Instance Variables 145 146 InputStream in; 147 OutputStream out; 148 LineReader lineReader; 149 ServerSocket ss; 150 151 private String finalResponse; 152 153 SimulatedGsmCallState simulatedCallState; 154 155 HandlerThread mHandlerThread; 156 157 int pausedResponseCount; 158 Object pausedResponseMonitor = new Object(); 159 160 //***** Events 161 162 static final int PROGRESS_CALL_STATE = 1; 163 164 //***** Constructor 165 166 public 167 ModelInterpreter (InputStream in, OutputStream out) 168 { 169 this.in = in; 170 this.out = out; 171 172 init(); 173 } 174 175 public 176 ModelInterpreter (InetSocketAddress sa) throws java.io.IOException 177 { 178 ss = new ServerSocket(); 179 180 ss.setReuseAddress(true); 181 ss.bind(sa); 182 183 init(); 184 } 185 186 private void 187 init() 188 { 189 new Thread(this, "ModelInterpreter").start(); 190 mHandlerThread = new HandlerThread("ModelInterpreter"); 191 mHandlerThread.start(); 192 Looper looper = mHandlerThread.getLooper(); 193 simulatedCallState = new SimulatedGsmCallState(looper); 194 } 195 196 //***** Runnable Implementation 197 198 public void run() 199 { 200 for (;;) { 201 if (ss != null) { 202 Socket s; 203 204 try { 205 s = ss.accept(); 206 } catch (java.io.IOException ex) { 207 Log.w(LOG_TAG, 208 "IOException on socket.accept(); stopping", ex); 209 return; 210 } 211 212 try { 213 in = s.getInputStream(); 214 out = s.getOutputStream(); 215 } catch (java.io.IOException ex) { 216 Log.w(LOG_TAG, 217 "IOException on accepted socket(); re-listening", ex); 218 continue; 219 } 220 221 Log.i(LOG_TAG, "New connection accepted"); 222 } 223 224 225 lineReader = new LineReader (in); 226 227 println ("Welcome"); 228 229 for (;;) { 230 String line; 231 232 line = lineReader.getNextLine(); 233 234 //System.out.println("MI<< " + line); 235 236 if (line == null) { 237 break; 238 } 239 240 synchronized(pausedResponseMonitor) { 241 while (pausedResponseCount > 0) { 242 try { 243 pausedResponseMonitor.wait(); 244 } catch (InterruptedException ex) { 245 } 246 } 247 } 248 249 synchronized (this) { 250 try { 251 finalResponse = "OK"; 252 processLine(line); 253 println(finalResponse); 254 } catch (InterpreterEx ex) { 255 println(ex.result); 256 } catch (RuntimeException ex) { 257 ex.printStackTrace(); 258 println("ERROR"); 259 } 260 } 261 } 262 263 Log.i(LOG_TAG, "Disconnected"); 264 265 if (ss == null) { 266 // no reconnect in this case 267 break; 268 } 269 } 270 } 271 272 273 //***** Instance Methods 274 275 /** Start the simulated phone ringing */ 276 public void 277 triggerRing(String number) 278 { 279 synchronized (this) { 280 boolean success; 281 282 success = simulatedCallState.triggerRing(number); 283 284 if (success) { 285 println ("RING"); 286 } 287 } 288 } 289 290 /** If a call is DIALING or ALERTING, progress it to the next state */ 291 public void 292 progressConnectingCallState() 293 { 294 simulatedCallState.progressConnectingCallState(); 295 } 296 297 298 /** If a call is DIALING or ALERTING, progress it all the way to ACTIVE */ 299 public void 300 progressConnectingToActive() 301 { 302 simulatedCallState.progressConnectingToActive(); 303 } 304 305 /** automatically progress mobile originated calls to ACTIVE. 306 * default to true 307 */ 308 public void 309 setAutoProgressConnectingCall(boolean b) 310 { 311 simulatedCallState.setAutoProgressConnectingCall(b); 312 } 313 314 public void 315 setNextDialFailImmediately(boolean b) 316 { 317 simulatedCallState.setNextDialFailImmediately(b); 318 } 319 320 public void setNextCallFailCause(int gsmCause) 321 { 322 //FIXME implement 323 } 324 325 326 /** hangup ringing, dialing, or actuve calls */ 327 public void 328 triggerHangupForeground() 329 { 330 boolean success; 331 332 success = simulatedCallState.triggerHangupForeground(); 333 334 if (success) { 335 println ("NO CARRIER"); 336 } 337 } 338 339 /** hangup holding calls */ 340 public void 341 triggerHangupBackground() 342 { 343 boolean success; 344 345 success = simulatedCallState.triggerHangupBackground(); 346 347 if (success) { 348 println ("NO CARRIER"); 349 } 350 } 351 352 /** hangup all */ 353 354 public void 355 triggerHangupAll() 356 { 357 boolean success; 358 359 success = simulatedCallState.triggerHangupAll(); 360 361 if (success) { 362 println ("NO CARRIER"); 363 } 364 } 365 366 public void 367 sendUnsolicited (String unsol) 368 { 369 synchronized (this) { 370 println(unsol); 371 } 372 } 373 374 public void triggerSsn(int a, int b) {} 375 public void triggerIncomingUssd(String statusCode, String message) {} 376 377 public void 378 triggerIncomingSMS(String message) 379 { 380/************** 381 StringBuilder pdu = new StringBuilder(); 382 383 pdu.append ("00"); //SMSC address - 0 bytes 384 385 pdu.append ("04"); // Message type indicator 386 387 // source address: +18005551212 388 pdu.append("918100551521F0"); 389 390 // protocol ID and data coding scheme 391 pdu.append("0000"); 392 393 Calendar c = Calendar.getInstance(); 394 395 pdu.append (c. 396 397 398 399 synchronized (this) { 400 println("+CMT: ,1\r" + pdu.toString()); 401 } 402 403**************/ 404 } 405 406 public void 407 pauseResponses() 408 { 409 synchronized(pausedResponseMonitor) { 410 pausedResponseCount++; 411 } 412 } 413 414 public void 415 resumeResponses() 416 { 417 synchronized(pausedResponseMonitor) { 418 pausedResponseCount--; 419 420 if (pausedResponseCount == 0) { 421 pausedResponseMonitor.notifyAll(); 422 } 423 } 424 } 425 426 //***** Private Instance Methods 427 428 private void 429 onAnswer() throws InterpreterEx 430 { 431 boolean success; 432 433 success = simulatedCallState.onAnswer(); 434 435 if (!success) { 436 throw new InterpreterEx("ERROR"); 437 } 438 } 439 440 private void 441 onHangup() throws InterpreterEx 442 { 443 boolean success = false; 444 445 success = simulatedCallState.onAnswer(); 446 447 if (!success) { 448 throw new InterpreterEx("ERROR"); 449 } 450 451 finalResponse = "NO CARRIER"; 452 } 453 454 private void 455 onCHLD(String command) throws InterpreterEx 456 { 457 // command starts with "+CHLD=" 458 char c0; 459 char c1 = 0; 460 boolean success; 461 462 c0 = command.charAt(6); 463 464 if (command.length() >= 8) { 465 c1 = command.charAt(7); 466 } 467 468 success = simulatedCallState.onChld(c0, c1); 469 470 if (!success) { 471 throw new InterpreterEx("ERROR"); 472 } 473 } 474 475 private void 476 releaseHeldOrUDUB() throws InterpreterEx 477 { 478 boolean success; 479 480 success = simulatedCallState.releaseHeldOrUDUB(); 481 482 if (!success) { 483 throw new InterpreterEx("ERROR"); 484 } 485 } 486 487 private void 488 releaseActiveAcceptHeldOrWaiting() throws InterpreterEx 489 { 490 boolean success; 491 492 success = simulatedCallState.releaseActiveAcceptHeldOrWaiting(); 493 494 if (!success) { 495 throw new InterpreterEx("ERROR"); 496 } 497 } 498 499 private void 500 switchActiveAndHeldOrWaiting() throws InterpreterEx 501 { 502 boolean success; 503 504 success = simulatedCallState.switchActiveAndHeldOrWaiting(); 505 506 if (!success) { 507 throw new InterpreterEx("ERROR"); 508 } 509 } 510 511 private void 512 separateCall(int index) throws InterpreterEx 513 { 514 boolean success; 515 516 success = simulatedCallState.separateCall(index); 517 518 if (!success) { 519 throw new InterpreterEx("ERROR"); 520 } 521 } 522 523 private void 524 conference() throws InterpreterEx 525 { 526 boolean success; 527 528 success = simulatedCallState.conference(); 529 530 if (!success) { 531 throw new InterpreterEx("ERROR"); 532 } 533 } 534 535 private void 536 onDial(String command) throws InterpreterEx 537 { 538 boolean success; 539 540 success = simulatedCallState.onDial(command.substring(1)); 541 542 if (!success) { 543 throw new InterpreterEx("ERROR"); 544 } 545 } 546 547 private void 548 onCLCC() throws InterpreterEx 549 { 550 List<String> lines; 551 552 lines = simulatedCallState.getClccLines(); 553 554 for (int i = 0, s = lines.size() ; i < s ; i++) { 555 println (lines.get(i)); 556 } 557 } 558 559 private void 560 onSMSSend(String command) throws InterpreterEx 561 { 562 String pdu; 563 564 print ("> "); 565 pdu = lineReader.getNextLineCtrlZ(); 566 567 println("+CMGS: 1"); 568 } 569 570 void 571 processLine (String line) throws InterpreterEx 572 { 573 String[] commands; 574 575 commands = splitCommands(line); 576 577 for (int i = 0; i < commands.length ; i++) { 578 String command = commands[i]; 579 580 if (command.equals("A")) { 581 onAnswer(); 582 } else if (command.equals("H")) { 583 onHangup(); 584 } else if (command.startsWith("+CHLD=")) { 585 onCHLD(command); 586 } else if (command.equals("+CLCC")) { 587 onCLCC(); 588 } else if (command.startsWith("D")) { 589 onDial(command); 590 } else if (command.startsWith("+CMGS=")) { 591 onSMSSend(command); 592 } else { 593 boolean found = false; 594 595 for (int j = 0; j < sDefaultResponses.length ; j++) { 596 if (command.equals(sDefaultResponses[j][0])) { 597 String r = sDefaultResponses[j][1]; 598 if (r != null) { 599 println(r); 600 } 601 found = true; 602 break; 603 } 604 } 605 606 if (!found) { 607 throw new InterpreterEx ("ERROR"); 608 } 609 } 610 } 611 } 612 613 614 String[] 615 splitCommands(String line) throws InterpreterEx 616 { 617 if (!line.startsWith ("AT")) { 618 throw new InterpreterEx("ERROR"); 619 } 620 621 if (line.length() == 2) { 622 // Just AT by itself 623 return new String[0]; 624 } 625 626 String ret[] = new String[1]; 627 628 //TODO fix case here too 629 ret[0] = line.substring(2); 630 631 return ret; 632/**** 633 try { 634 // i = 2 to skip over AT 635 for (int i = 2, s = line.length() ; i < s ; i++) { 636 // r"|([A-RT-Z]\d?)" # Normal commands eg ATA or I0 637 // r"|(&[A-Z]\d*)" # & commands eg &C 638 // r"|(S\d+(=\d+)?)" # S registers 639 // r"((\+|%)\w+(\?|=([^;]+(;|$)))?)" # extended command eg +CREG=2 640 641 642 } 643 } catch (StringIndexOutOfBoundsException ex) { 644 throw new InterpreterEx ("ERROR"); 645 } 646***/ 647 } 648 649 void 650 println (String s) 651 { 652 synchronized(this) { 653 try { 654 byte[] bytes = s.getBytes("US-ASCII"); 655 656 //System.out.println("MI>> " + s); 657 658 out.write(bytes); 659 out.write('\r'); 660 } catch (IOException ex) { 661 ex.printStackTrace(); 662 } 663 } 664 } 665 666 void 667 print (String s) 668 { 669 synchronized(this) { 670 try { 671 byte[] bytes = s.getBytes("US-ASCII"); 672 673 //System.out.println("MI>> " + s + " (no <cr>)"); 674 675 out.write(bytes); 676 } catch (IOException ex) { 677 ex.printStackTrace(); 678 } 679 } 680 } 681 682 683 public void 684 shutdown() 685 { 686 Looper looper = mHandlerThread.getLooper(); 687 if (looper != null) { 688 looper.quit(); 689 } 690 691 try { 692 in.close(); 693 } catch (IOException ex) { 694 } 695 try { 696 out.close(); 697 } catch (IOException ex) { 698 } 699 } 700 701 702 static final String [][] sDefaultResponses = { 703 {"E0Q0V1", null}, 704 {"+CMEE=2", null}, 705 {"+CREG=2", null}, 706 {"+CGREG=2", null}, 707 {"+CCWA=1", null}, 708 {"+COPS=0", null}, 709 {"+CFUN=1", null}, 710 {"+CGMI", "+CGMI: Android Model AT Interpreter\r"}, 711 {"+CGMM", "+CGMM: Android Model AT Interpreter\r"}, 712 {"+CGMR", "+CGMR: 1.0\r"}, 713 {"+CGSN", "000000000000000\r"}, 714 {"+CIMI", "320720000000000\r"}, 715 {"+CSCS=?", "+CSCS: (\"HEX\",\"UCS2\")\r"}, 716 {"+CFUN?", "+CFUN: 1\r"}, 717 {"+COPS=3,0;+COPS?;+COPS=3,1;+COPS?;+COPS=3,2;+COPS?", 718 "+COPS: 0,0,\"Android\"\r" 719 + "+COPS: 0,1,\"Android\"\r" 720 + "+COPS: 0,2,\"310995\"\r"}, 721 {"+CREG?", "+CREG: 2,5, \"0113\", \"6614\"\r"}, 722 {"+CGREG?", "+CGREG: 2,0\r"}, 723 {"+CSQ", "+CSQ: 16,99\r"}, 724 {"+CNMI?", "+CNMI: 1,2,2,1,1\r"}, 725 {"+CLIR?", "+CLIR: 1,3\r"}, 726 {"%CPVWI=2", "%CPVWI: 0\r"}, 727 {"+CUSD=1,\"#646#\"", "+CUSD=0,\"You have used 23 minutes\"\r"}, 728 {"+CRSM=176,12258,0,0,10", "+CRSM: 144,0,981062200050259429F6\r"}, 729 {"+CRSM=192,12258,0,0,15", "+CRSM: 144,0,0000000A2FE204000FF55501020000\r"}, 730 731 /* EF[ADN] */ 732 {"+CRSM=192,28474,0,0,15", "+CRSM: 144,0,0000005a6f3a040011f5220102011e\r"}, 733 {"+CRSM=178,28474,1,4,30", "+CRSM: 144,0,437573746f6d65722043617265ffffff07818100398799f7ffffffffffff\r"}, 734 {"+CRSM=178,28474,2,4,30", "+CRSM: 144,0,566f696365204d61696cffffffffffff07918150367742f3ffffffffffff\r"}, 735 {"+CRSM=178,28474,3,4,30", "+CRSM: 144,0,4164676a6dffffffffffffffffffffff0b918188551512c221436587ff01\r"}, 736 {"+CRSM=178,28474,4,4,30", "+CRSM: 144,0,810101c1ffffffffffffffffffffffff068114455245f8ffffffffffffff\r"}, 737 /* EF[EXT1] */ 738 {"+CRSM=192,28490,0,0,15", "+CRSM: 144,0,000000416f4a040011f5550102010d\r"}, 739 {"+CRSM=178,28490,1,4,13", "+CRSM: 144,0,0206092143658709ffffffffff\r"} 740 }; 741} 742