ModelInterpreter.java revision 99c2e1d6749cfad2a8ca94a47857d8c3bfc09454
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.telephony.Rlog;
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                    Rlog.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                    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            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            Rlog.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