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