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