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