blob: ddb99d981e9d99e8e8628de9a7d76102d9e10d67 [file] [log] [blame]
/*
* Copyright (C) 2006 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.internal.telephony.test;
import android.compat.annotation.UnsupportedAppUsage;
import android.os.HandlerThread;
import android.os.Looper;
import com.android.telephony.Rlog;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.UnsupportedEncodingException;
import java.net.InetSocketAddress;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.List;
// Also in ATChannel.java
class LineReader
{
/**
* Not threadsafe
* Assumes input is ASCII
*/
//***** Constants
// For what it's worth, this is also the size of an
// OMAP CSMI mailbox
static final int BUFFER_SIZE = 0x1000;
// just to prevent constant allocations
byte mBuffer[] = new byte[BUFFER_SIZE];
//***** Instance Variables
InputStream mInStream;
LineReader (InputStream s)
{
mInStream = s;
}
String
getNextLine()
{
return getNextLine(false);
}
String
getNextLineCtrlZ()
{
return getNextLine(true);
}
/**
* Note: doesn't return the last incomplete line read on EOF, since
* it doesn't typically matter anyway
*
* Returns NULL on EOF
*/
String
getNextLine(boolean ctrlZ)
{
int i = 0;
try {
for (;;) {
int result;
result = mInStream.read();
if (result < 0) {
return null;
}
if (ctrlZ && result == 0x1a) {
break;
} else if (result == '\r' || result == '\n') {
if (i == 0) {
// Skip leading cr/lf
continue;
} else {
break;
}
}
mBuffer[i++] = (byte)result;
}
} catch (IOException ex) {
return null;
} catch (IndexOutOfBoundsException ex) {
System.err.println("ATChannel: buffer overflow");
}
try {
return new String(mBuffer, 0, i, "US-ASCII");
} catch (UnsupportedEncodingException ex) {
System.err.println("ATChannel: implausable UnsupportedEncodingException");
return null;
}
}
}
class InterpreterEx extends Exception
{
@UnsupportedAppUsage
public
InterpreterEx (String result)
{
mResult = result;
}
String mResult;
}
public class ModelInterpreter
implements Runnable, SimulatedRadioControl
{
static final int MAX_CALLS = 6;
/** number of msec between dialing -> alerting and alerting->active */
static final int CONNECTING_PAUSE_MSEC = 5 * 100;
static final String LOG_TAG = "ModelInterpreter";
//***** Instance Variables
InputStream mIn;
OutputStream mOut;
LineReader mLineReader;
ServerSocket mSS;
private String mFinalResponse;
SimulatedGsmCallState mSimulatedCallState;
HandlerThread mHandlerThread;
int mPausedResponseCount;
Object mPausedResponseMonitor = new Object();
//***** Events
static final int PROGRESS_CALL_STATE = 1;
//***** Constructor
public
ModelInterpreter (InputStream in, OutputStream out)
{
mIn = in;
mOut = out;
init();
}
public
ModelInterpreter (InetSocketAddress sa) throws java.io.IOException
{
mSS = new ServerSocket();
mSS.setReuseAddress(true);
mSS.bind(sa);
init();
}
private void
init()
{
new Thread(this, "ModelInterpreter").start();
mHandlerThread = new HandlerThread("ModelInterpreter");
mHandlerThread.start();
Looper looper = mHandlerThread.getLooper();
mSimulatedCallState = new SimulatedGsmCallState(looper);
}
//***** Runnable Implementation
@Override
public void run()
{
for (;;) {
if (mSS != null) {
Socket s;
try {
s = mSS.accept();
} catch (java.io.IOException ex) {
Rlog.w(LOG_TAG,
"IOException on socket.accept(); stopping", ex);
return;
}
try {
mIn = s.getInputStream();
mOut = s.getOutputStream();
} catch (java.io.IOException ex) {
Rlog.w(LOG_TAG,
"IOException on accepted socket(); re-listening", ex);
continue;
}
Rlog.i(LOG_TAG, "New connection accepted");
}
mLineReader = new LineReader (mIn);
println ("Welcome");
for (;;) {
String line;
line = mLineReader.getNextLine();
//System.out.println("MI<< " + line);
if (line == null) {
break;
}
synchronized(mPausedResponseMonitor) {
while (mPausedResponseCount > 0) {
try {
mPausedResponseMonitor.wait();
} catch (InterruptedException ex) {
}
}
}
synchronized (this) {
try {
mFinalResponse = "OK";
processLine(line);
println(mFinalResponse);
} catch (InterpreterEx ex) {
println(ex.mResult);
} catch (RuntimeException ex) {
ex.printStackTrace();
println("ERROR");
}
}
}
Rlog.i(LOG_TAG, "Disconnected");
if (mSS == null) {
// no reconnect in this case
break;
}
}
}
//***** Instance Methods
/** Start the simulated phone ringing */
@Override
public void
triggerRing(String number)
{
synchronized (this) {
boolean success;
success = mSimulatedCallState.triggerRing(number);
if (success) {
println ("RING");
}
}
}
/** If a call is DIALING or ALERTING, progress it to the next state */
@Override
public void
progressConnectingCallState()
{
mSimulatedCallState.progressConnectingCallState();
}
/** If a call is DIALING or ALERTING, progress it all the way to ACTIVE */
@Override
public void
progressConnectingToActive()
{
mSimulatedCallState.progressConnectingToActive();
}
/** automatically progress mobile originated calls to ACTIVE.
* default to true
*/
@Override
public void
setAutoProgressConnectingCall(boolean b)
{
mSimulatedCallState.setAutoProgressConnectingCall(b);
}
@Override
public void
setNextDialFailImmediately(boolean b)
{
mSimulatedCallState.setNextDialFailImmediately(b);
}
@Override
public void setNextCallFailCause(int gsmCause)
{
//FIXME implement
}
/** hangup ringing, dialing, or actuve calls */
@Override
public void
triggerHangupForeground()
{
boolean success;
success = mSimulatedCallState.triggerHangupForeground();
if (success) {
println ("NO CARRIER");
}
}
/** hangup holding calls */
@Override
public void
triggerHangupBackground()
{
boolean success;
success = mSimulatedCallState.triggerHangupBackground();
if (success) {
println ("NO CARRIER");
}
}
/** hangup all */
@Override
public void
triggerHangupAll()
{
boolean success;
success = mSimulatedCallState.triggerHangupAll();
if (success) {
println ("NO CARRIER");
}
}
public void
sendUnsolicited (String unsol)
{
synchronized (this) {
println(unsol);
}
}
@Override
public void triggerSsn(int a, int b) {}
@Override
public void triggerIncomingUssd(String statusCode, String message) {}
@Override
public void
triggerIncomingSMS(String message)
{
/**************
StringBuilder pdu = new StringBuilder();
pdu.append ("00"); //SMSC address - 0 bytes
pdu.append ("04"); // Message type indicator
// source address: +18005551212
pdu.append("918100551521F0");
// protocol ID and data coding scheme
pdu.append("0000");
Calendar c = Calendar.getInstance();
pdu.append (c.
synchronized (this) {
println("+CMT: ,1\r" + pdu.toString());
}
**************/
}
@Override
public void
pauseResponses()
{
synchronized(mPausedResponseMonitor) {
mPausedResponseCount++;
}
}
@Override
public void
resumeResponses()
{
synchronized(mPausedResponseMonitor) {
mPausedResponseCount--;
if (mPausedResponseCount == 0) {
mPausedResponseMonitor.notifyAll();
}
}
}
//***** Private Instance Methods
private void
onAnswer() throws InterpreterEx
{
boolean success;
success = mSimulatedCallState.onAnswer();
if (!success) {
throw new InterpreterEx("ERROR");
}
}
private void
onHangup() throws InterpreterEx
{
boolean success = false;
success = mSimulatedCallState.onAnswer();
if (!success) {
throw new InterpreterEx("ERROR");
}
mFinalResponse = "NO CARRIER";
}
private void
onCHLD(String command) throws InterpreterEx
{
// command starts with "+CHLD="
char c0;
char c1 = 0;
boolean success;
c0 = command.charAt(6);
if (command.length() >= 8) {
c1 = command.charAt(7);
}
success = mSimulatedCallState.onChld(c0, c1);
if (!success) {
throw new InterpreterEx("ERROR");
}
}
private void
onDial(String command) throws InterpreterEx
{
boolean success;
success = mSimulatedCallState.onDial(command.substring(1));
if (!success) {
throw new InterpreterEx("ERROR");
}
}
private void
onCLCC()
{
List<String> lines;
lines = mSimulatedCallState.getClccLines();
for (int i = 0, s = lines.size() ; i < s ; i++) {
println (lines.get(i));
}
}
private void
onSMSSend(String command)
{
String pdu;
print ("> ");
pdu = mLineReader.getNextLineCtrlZ();
println("+CMGS: 1");
}
void
processLine (String line) throws InterpreterEx
{
String[] commands;
commands = splitCommands(line);
for (int i = 0; i < commands.length ; i++) {
String command = commands[i];
if (command.equals("A")) {
onAnswer();
} else if (command.equals("H")) {
onHangup();
} else if (command.startsWith("+CHLD=")) {
onCHLD(command);
} else if (command.equals("+CLCC")) {
onCLCC();
} else if (command.startsWith("D")) {
onDial(command);
} else if (command.startsWith("+CMGS=")) {
onSMSSend(command);
} else {
boolean found = false;
for (int j = 0; j < sDefaultResponses.length ; j++) {
if (command.equals(sDefaultResponses[j][0])) {
String r = sDefaultResponses[j][1];
if (r != null) {
println(r);
}
found = true;
break;
}
}
if (!found) {
throw new InterpreterEx ("ERROR");
}
}
}
}
String[]
splitCommands(String line) throws InterpreterEx
{
if (!line.startsWith ("AT")) {
throw new InterpreterEx("ERROR");
}
if (line.length() == 2) {
// Just AT by itself
return new String[0];
}
String ret[] = new String[1];
//TODO fix case here too
ret[0] = line.substring(2);
return ret;
/****
try {
// i = 2 to skip over AT
for (int i = 2, s = line.length() ; i < s ; i++) {
// r"|([A-RT-Z]\d?)" # Normal commands eg ATA or I0
// r"|(&[A-Z]\d*)" # & commands eg &C
// r"|(S\d+(=\d+)?)" # S registers
// r"((\+|%)\w+(\?|=([^;]+(;|$)))?)" # extended command eg +CREG=2
}
} catch (StringIndexOutOfBoundsException ex) {
throw new InterpreterEx ("ERROR");
}
***/
}
void
println (String s)
{
synchronized(this) {
try {
byte[] bytes = s.getBytes("US-ASCII");
//System.out.println("MI>> " + s);
mOut.write(bytes);
mOut.write('\r');
} catch (IOException ex) {
ex.printStackTrace();
}
}
}
void
print (String s)
{
synchronized(this) {
try {
byte[] bytes = s.getBytes("US-ASCII");
//System.out.println("MI>> " + s + " (no <cr>)");
mOut.write(bytes);
} catch (IOException ex) {
ex.printStackTrace();
}
}
}
@Override
public void
shutdown()
{
Looper looper = mHandlerThread.getLooper();
if (looper != null) {
looper.quit();
}
try {
mIn.close();
} catch (IOException ex) {
}
try {
mOut.close();
} catch (IOException ex) {
}
}
static final String [][] sDefaultResponses = {
{"E0Q0V1", null},
{"+CMEE=2", null},
{"+CREG=2", null},
{"+CGREG=2", null},
{"+CCWA=1", null},
{"+COPS=0", null},
{"+CFUN=1", null},
{"+CGMI", "+CGMI: Android Model AT Interpreter\r"},
{"+CGMM", "+CGMM: Android Model AT Interpreter\r"},
{"+CGMR", "+CGMR: 1.0\r"},
{"+CGSN", "000000000000000\r"},
{"+CIMI", "320720000000000\r"},
{"+CSCS=?", "+CSCS: (\"HEX\",\"UCS2\")\r"},
{"+CFUN?", "+CFUN: 1\r"},
{"+COPS=3,0;+COPS?;+COPS=3,1;+COPS?;+COPS=3,2;+COPS?",
"+COPS: 0,0,\"Android\"\r"
+ "+COPS: 0,1,\"Android\"\r"
+ "+COPS: 0,2,\"310995\"\r"},
{"+CREG?", "+CREG: 2,5, \"0113\", \"6614\"\r"},
{"+CGREG?", "+CGREG: 2,0\r"},
{"+CSQ", "+CSQ: 16,99\r"},
{"+CNMI?", "+CNMI: 1,2,2,1,1\r"},
{"+CLIR?", "+CLIR: 1,3\r"},
{"%CPVWI=2", "%CPVWI: 0\r"},
{"+CUSD=1,\"#646#\"", "+CUSD=0,\"You have used 23 minutes\"\r"},
{"+CRSM=176,12258,0,0,10", "+CRSM: 144,0,981062200050259429F6\r"},
{"+CRSM=192,12258,0,0,15", "+CRSM: 144,0,0000000A2FE204000FF55501020000\r"},
/* EF[ADN] */
{"+CRSM=192,28474,0,0,15", "+CRSM: 144,0,0000005a6f3a040011f5220102011e\r"},
{"+CRSM=178,28474,1,4,30", "+CRSM: 144,0,437573746f6d65722043617265ffffff07818100398799f7ffffffffffff\r"},
{"+CRSM=178,28474,2,4,30", "+CRSM: 144,0,566f696365204d61696cffffffffffff07918150367742f3ffffffffffff\r"},
{"+CRSM=178,28474,3,4,30", "+CRSM: 144,0,4164676a6dffffffffffffffffffffff0b918188551512c221436587ff01\r"},
{"+CRSM=178,28474,4,4,30", "+CRSM: 144,0,810101c1ffffffffffffffffffffffff068114455245f8ffffffffffffff\r"},
/* EF[EXT1] */
{"+CRSM=192,28490,0,0,15", "+CRSM: 144,0,000000416f4a040011f5550102010d\r"},
{"+CRSM=178,28490,1,4,13", "+CRSM: 144,0,0206092143658709ffffffffff\r"}
};
}