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.Looper;
20import android.os.Message;
21import android.os.Handler;
22import android.telephony.PhoneNumberUtils;
23import com.android.internal.telephony.ATParseEx;
24import com.android.internal.telephony.DriverCall;
25import java.util.List;
26import java.util.ArrayList;
27
28import android.util.Log;
29
30class CallInfo {
31    enum State {
32        ACTIVE(0),
33        HOLDING(1),
34        DIALING(2),    // MO call only
35        ALERTING(3),   // MO call only
36        INCOMING(4),   // MT call only
37        WAITING(5);    // MT call only
38
39        State (int value) {this.value = value;}
40
41        private final int value;
42        public int value() {return value;};
43    };
44
45    boolean isMT;
46    State state;
47    boolean isMpty;
48    String number;
49    int TOA;
50
51    CallInfo (boolean isMT, State state, boolean isMpty, String number) {
52        this.isMT = isMT;
53        this.state = state;
54        this.isMpty = isMpty;
55        this.number = number;
56
57        if (number.length() > 0 && number.charAt(0) == '+') {
58            TOA = PhoneNumberUtils.TOA_International;
59        } else {
60            TOA = PhoneNumberUtils.TOA_Unknown;
61        }
62    }
63
64    static CallInfo
65    createOutgoingCall(String number) {
66        return new CallInfo (false, State.DIALING, false, number);
67    }
68
69    static CallInfo
70    createIncomingCall(String number) {
71        return new CallInfo (true, State.INCOMING, false, number);
72    }
73
74    String
75    toCLCCLine(int index) {
76        return
77            "+CLCC: "
78            + index + "," + (isMT ? "1" : "0") +","
79            + state.value() + ",0," + (isMpty ? "1" : "0")
80            + ",\"" + number + "\"," + TOA;
81    }
82
83    DriverCall
84    toDriverCall(int index) {
85        DriverCall ret;
86
87        ret = new DriverCall();
88
89        ret.index = index;
90        ret.isMT = isMT;
91
92        try {
93            ret.state = DriverCall.stateFromCLCC(state.value());
94        } catch (ATParseEx ex) {
95            throw new RuntimeException("should never happen", ex);
96        }
97
98        ret.isMpty = isMpty;
99        ret.number = number;
100        ret.TOA = TOA;
101        ret.isVoice = true;
102        ret.als = 0;
103
104        return ret;
105    }
106
107
108    boolean
109    isActiveOrHeld() {
110        return state == State.ACTIVE || state == State.HOLDING;
111    }
112
113    boolean
114    isConnecting() {
115        return state == State.DIALING || state == State.ALERTING;
116    }
117
118    boolean
119    isRinging() {
120        return state == State.INCOMING || state == State.WAITING;
121    }
122
123}
124
125class InvalidStateEx extends Exception {
126    InvalidStateEx() {
127
128    }
129}
130
131
132class SimulatedGsmCallState extends Handler {
133    //***** Instance Variables
134
135    CallInfo calls[] = new CallInfo[MAX_CALLS];
136
137    private boolean autoProgressConnecting = true;
138    private boolean nextDialFailImmediately;
139
140
141    //***** Event Constants
142
143    static final int EVENT_PROGRESS_CALL_STATE = 1;
144
145    //***** Constants
146
147    static final int MAX_CALLS = 7;
148    /** number of msec between dialing -> alerting and alerting->active */
149    static final int CONNECTING_PAUSE_MSEC = 5 * 100;
150
151
152    //***** Overridden from Handler
153
154    public SimulatedGsmCallState(Looper looper) {
155        super(looper);
156    }
157
158    public void
159    handleMessage(Message msg) {
160        synchronized(this) { switch (msg.what) {
161            // PLEASE REMEMBER
162            // calls may have hung up by the time delayed events happen
163
164            case EVENT_PROGRESS_CALL_STATE:
165                progressConnectingCallState();
166            break;
167        }}
168    }
169
170    //***** Public Methods
171
172    /**
173     * Start the simulated phone ringing
174     * true if succeeded, false if failed
175     */
176    public boolean
177    triggerRing(String number) {
178        synchronized (this) {
179            int empty = -1;
180            boolean isCallWaiting = false;
181
182            // ensure there aren't already calls INCOMING or WAITING
183            for (int i = 0 ; i < calls.length ; i++) {
184                CallInfo call = calls[i];
185
186                if (call == null && empty < 0) {
187                    empty = i;
188                } else if (call != null
189                    && (call.state == CallInfo.State.INCOMING
190                        || call.state == CallInfo.State.WAITING)
191                ) {
192                    Log.w("ModelInterpreter",
193                        "triggerRing failed; phone already ringing");
194                    return false;
195                } else if (call != null) {
196                    isCallWaiting = true;
197                }
198            }
199
200            if (empty < 0 ) {
201                Log.w("ModelInterpreter", "triggerRing failed; all full");
202                return false;
203            }
204
205            calls[empty] = CallInfo.createIncomingCall(
206                PhoneNumberUtils.extractNetworkPortion(number));
207
208            if (isCallWaiting) {
209                calls[empty].state = CallInfo.State.WAITING;
210            }
211
212        }
213        return true;
214    }
215
216    /** If a call is DIALING or ALERTING, progress it to the next state */
217    public void
218    progressConnectingCallState() {
219        synchronized (this)  {
220            for (int i = 0 ; i < calls.length ; i++) {
221                CallInfo call = calls[i];
222
223                if (call != null && call.state == CallInfo.State.DIALING) {
224                    call.state = CallInfo.State.ALERTING;
225
226                    if (autoProgressConnecting) {
227                        sendMessageDelayed(
228                                obtainMessage(EVENT_PROGRESS_CALL_STATE, call),
229                                CONNECTING_PAUSE_MSEC);
230                    }
231                    break;
232                } else if (call != null
233                        && call.state == CallInfo.State.ALERTING
234                ) {
235                    call.state = CallInfo.State.ACTIVE;
236                    break;
237                }
238            }
239        }
240    }
241
242    /** If a call is DIALING or ALERTING, progress it all the way to ACTIVE */
243    public void
244    progressConnectingToActive() {
245        synchronized (this)  {
246            for (int i = 0 ; i < calls.length ; i++) {
247                CallInfo call = calls[i];
248
249                if (call != null && (call.state == CallInfo.State.DIALING
250                    || call.state == CallInfo.State.ALERTING)
251                ) {
252                    call.state = CallInfo.State.ACTIVE;
253                    break;
254                }
255            }
256        }
257    }
258
259    /** automatically progress mobile originated calls to ACTIVE.
260     *  default to true
261     */
262    public void
263    setAutoProgressConnectingCall(boolean b) {
264        autoProgressConnecting = b;
265    }
266
267    public void
268    setNextDialFailImmediately(boolean b) {
269        nextDialFailImmediately = b;
270    }
271
272    /**
273     * hangup ringing, dialing, or active calls
274     * returns true if call was hung up, false if not
275     */
276    public boolean
277    triggerHangupForeground() {
278        synchronized (this) {
279            boolean found;
280
281            found = false;
282
283            for (int i = 0 ; i < calls.length ; i++) {
284                CallInfo call = calls[i];
285
286                if (call != null
287                    && (call.state == CallInfo.State.INCOMING
288                        || call.state == CallInfo.State.WAITING)
289                ) {
290                    calls[i] = null;
291                    found = true;
292                }
293            }
294
295            for (int i = 0 ; i < calls.length ; i++) {
296                CallInfo call = calls[i];
297
298                if (call != null
299                    && (call.state == CallInfo.State.DIALING
300                        || call.state == CallInfo.State.ACTIVE
301                        || call.state == CallInfo.State.ALERTING)
302                ) {
303                    calls[i] = null;
304                    found = true;
305                }
306            }
307            return found;
308        }
309    }
310
311    /**
312     * hangup holding calls
313     * returns true if call was hung up, false if not
314     */
315    public boolean
316    triggerHangupBackground() {
317        synchronized (this) {
318            boolean found = false;
319
320            for (int i = 0 ; i < calls.length ; i++) {
321                CallInfo call = calls[i];
322
323                if (call != null && call.state == CallInfo.State.HOLDING) {
324                    calls[i] = null;
325                    found = true;
326                }
327            }
328
329            return found;
330        }
331    }
332
333    /**
334     * hangup all
335     * returns true if call was hung up, false if not
336     */
337    public boolean
338    triggerHangupAll() {
339        synchronized(this) {
340            boolean found = false;
341
342            for (int i = 0 ; i < calls.length ; i++) {
343                CallInfo call = calls[i];
344
345                if (calls[i] != null) {
346                    found = true;
347                }
348
349                calls[i] = null;
350            }
351
352            return found;
353        }
354    }
355
356    public boolean
357    onAnswer() {
358        synchronized (this) {
359            for (int i = 0 ; i < calls.length ; i++) {
360                CallInfo call = calls[i];
361
362                if (call != null
363                    && (call.state == CallInfo.State.INCOMING
364                        || call.state == CallInfo.State.WAITING)
365                ) {
366                    return switchActiveAndHeldOrWaiting();
367                }
368            }
369        }
370
371        return false;
372    }
373
374    public boolean
375    onHangup() {
376        boolean found = false;
377
378        for (int i = 0 ; i < calls.length ; i++) {
379            CallInfo call = calls[i];
380
381            if (call != null && call.state != CallInfo.State.WAITING) {
382                calls[i] = null;
383                found = true;
384            }
385        }
386
387        return found;
388    }
389
390    public boolean
391    onChld(char c0, char c1) {
392        boolean ret;
393        int callIndex = 0;
394
395        if (c1 != 0) {
396            callIndex = c1 - '1';
397
398            if (callIndex < 0 || callIndex >= calls.length) {
399                return false;
400            }
401        }
402
403        switch (c0) {
404            case '0':
405                ret = releaseHeldOrUDUB();
406            break;
407            case '1':
408                if (c1 <= 0) {
409                    ret = releaseActiveAcceptHeldOrWaiting();
410                } else {
411                    if (calls[callIndex] == null) {
412                        ret = false;
413                    } else {
414                        calls[callIndex] = null;
415                        ret = true;
416                    }
417                }
418            break;
419            case '2':
420                if (c1 <= 0) {
421                    ret = switchActiveAndHeldOrWaiting();
422                } else {
423                    ret = separateCall(callIndex);
424                }
425            break;
426            case '3':
427                ret = conference();
428            break;
429            case '4':
430                ret = explicitCallTransfer();
431            break;
432            case '5':
433                if (true) { //just so javac doesnt complain about break
434                    //CCBS not impled
435                    ret = false;
436                }
437            break;
438            default:
439                ret = false;
440
441        }
442
443        return ret;
444    }
445
446    public boolean
447    releaseHeldOrUDUB() {
448        boolean found = false;
449
450        for (int i = 0 ; i < calls.length ; i++) {
451            CallInfo c = calls[i];
452
453            if (c != null && c.isRinging()) {
454                found = true;
455                calls[i] = null;
456                break;
457            }
458        }
459
460        if (!found) {
461            for (int i = 0 ; i < calls.length ; i++) {
462                CallInfo c = calls[i];
463
464                if (c != null && c.state == CallInfo.State.HOLDING) {
465                    found = true;
466                    calls[i] = null;
467                    // don't stop...there may be more than one
468                }
469            }
470        }
471
472        return true;
473    }
474
475
476    public boolean
477    releaseActiveAcceptHeldOrWaiting() {
478        boolean foundHeld = false;
479        boolean foundActive = false;
480
481        for (int i = 0 ; i < calls.length ; i++) {
482            CallInfo c = calls[i];
483
484            if (c != null && c.state == CallInfo.State.ACTIVE) {
485                calls[i] = null;
486                foundActive = true;
487            }
488        }
489
490        if (!foundActive) {
491            // FIXME this may not actually be how most basebands react
492            // CHLD=1 may not hang up dialing/alerting calls
493            for (int i = 0 ; i < calls.length ; i++) {
494                CallInfo c = calls[i];
495
496                if (c != null
497                        && (c.state == CallInfo.State.DIALING
498                            || c.state == CallInfo.State.ALERTING)
499                ) {
500                    calls[i] = null;
501                    foundActive = true;
502                }
503            }
504        }
505
506        for (int i = 0 ; i < calls.length ; i++) {
507            CallInfo c = calls[i];
508
509            if (c != null && c.state == CallInfo.State.HOLDING) {
510                c.state = CallInfo.State.ACTIVE;
511                foundHeld = true;
512            }
513        }
514
515        if (foundHeld) {
516            return true;
517        }
518
519        for (int i = 0 ; i < calls.length ; i++) {
520            CallInfo c = calls[i];
521
522            if (c != null && c.isRinging()) {
523                c.state = CallInfo.State.ACTIVE;
524                return true;
525            }
526        }
527
528        return true;
529    }
530
531    public boolean
532    switchActiveAndHeldOrWaiting() {
533        boolean hasHeld = false;
534
535        // first, are there held calls?
536        for (int i = 0 ; i < calls.length ; i++) {
537            CallInfo c = calls[i];
538
539            if (c != null && c.state == CallInfo.State.HOLDING) {
540                hasHeld = true;
541                break;
542            }
543        }
544
545        // Now, switch
546        for (int i = 0 ; i < calls.length ; i++) {
547            CallInfo c = calls[i];
548
549            if (c != null) {
550                if (c.state == CallInfo.State.ACTIVE) {
551                    c.state = CallInfo.State.HOLDING;
552                } else if (c.state == CallInfo.State.HOLDING) {
553                    c.state = CallInfo.State.ACTIVE;
554                } else if (!hasHeld && c.isRinging())  {
555                    c.state = CallInfo.State.ACTIVE;
556                }
557            }
558        }
559
560        return true;
561    }
562
563
564    public boolean
565    separateCall(int index) {
566        try {
567            CallInfo c;
568
569            c = calls[index];
570
571            if (c == null || c.isConnecting() || countActiveLines() != 1) {
572                return false;
573            }
574
575            c.state = CallInfo.State.ACTIVE;
576            c.isMpty = false;
577
578            for (int i = 0 ; i < calls.length ; i++) {
579                int countHeld=0, lastHeld=0;
580
581                if (i != index) {
582                    CallInfo cb = calls[i];
583
584                    if (cb != null && cb.state == CallInfo.State.ACTIVE) {
585                        cb.state = CallInfo.State.HOLDING;
586                        countHeld++;
587                        lastHeld = i;
588                    }
589                }
590
591                if (countHeld == 1) {
592                    // if there's only one left, clear the MPTY flag
593                    calls[lastHeld].isMpty = false;
594                }
595            }
596
597            return true;
598        } catch (InvalidStateEx ex) {
599            return false;
600        }
601    }
602
603
604
605    public boolean
606    conference() {
607        int countCalls = 0;
608
609        // if there's connecting calls, we can't do this yet
610        for (int i = 0 ; i < calls.length ; i++) {
611            CallInfo c = calls[i];
612
613            if (c != null) {
614                countCalls++;
615
616                if (c.isConnecting()) {
617                    return false;
618                }
619            }
620        }
621        for (int i = 0 ; i < calls.length ; i++) {
622            CallInfo c = calls[i];
623
624            if (c != null) {
625                c.state = CallInfo.State.ACTIVE;
626                if (countCalls > 0) {
627                    c.isMpty = true;
628                }
629            }
630        }
631
632        return true;
633    }
634
635    public boolean
636    explicitCallTransfer() {
637        int countCalls = 0;
638
639        // if there's connecting calls, we can't do this yet
640        for (int i = 0 ; i < calls.length ; i++) {
641            CallInfo c = calls[i];
642
643            if (c != null) {
644                countCalls++;
645
646                if (c.isConnecting()) {
647                    return false;
648                }
649            }
650        }
651
652        // disconnect the subscriber from both calls
653        return triggerHangupAll();
654    }
655
656    public boolean
657    onDial(String address) {
658        CallInfo call;
659        int freeSlot = -1;
660
661        Log.d("GSM", "SC> dial '" + address + "'");
662
663        if (nextDialFailImmediately) {
664            nextDialFailImmediately = false;
665
666            Log.d("GSM", "SC< dial fail (per request)");
667            return false;
668        }
669
670        String phNum = PhoneNumberUtils.extractNetworkPortion(address);
671
672        if (phNum.length() == 0) {
673            Log.d("GSM", "SC< dial fail (invalid ph num)");
674            return false;
675        }
676
677        // Ignore setting up GPRS
678        if (phNum.startsWith("*99") && phNum.endsWith("#")) {
679            Log.d("GSM", "SC< dial ignored (gprs)");
680            return true;
681        }
682
683        // There can be at most 1 active "line" when we initiate
684        // a new call
685        try {
686            if (countActiveLines() > 1) {
687                Log.d("GSM", "SC< dial fail (invalid call state)");
688                return false;
689            }
690        } catch (InvalidStateEx ex) {
691            Log.d("GSM", "SC< dial fail (invalid call state)");
692            return false;
693        }
694
695        for (int i = 0 ; i < calls.length ; i++) {
696            if (freeSlot < 0 && calls[i] == null) {
697                freeSlot = i;
698            }
699
700            if (calls[i] != null && !calls[i].isActiveOrHeld()) {
701                // Can't make outgoing calls when there is a ringing or
702                // connecting outgoing call
703                Log.d("GSM", "SC< dial fail (invalid call state)");
704                return false;
705            } else if (calls[i] != null && calls[i].state == CallInfo.State.ACTIVE) {
706                // All active calls behome held
707                calls[i].state = CallInfo.State.HOLDING;
708            }
709        }
710
711        if (freeSlot < 0) {
712            Log.d("GSM", "SC< dial fail (invalid call state)");
713            return false;
714        }
715
716        calls[freeSlot] = CallInfo.createOutgoingCall(phNum);
717
718        if (autoProgressConnecting) {
719            sendMessageDelayed(
720                    obtainMessage(EVENT_PROGRESS_CALL_STATE, calls[freeSlot]),
721                    CONNECTING_PAUSE_MSEC);
722        }
723
724        Log.d("GSM", "SC< dial (slot = " + freeSlot + ")");
725
726        return true;
727    }
728
729    public List<DriverCall>
730    getDriverCalls() {
731        ArrayList<DriverCall> ret = new ArrayList<DriverCall>(calls.length);
732
733        for (int i = 0 ; i < calls.length ; i++) {
734            CallInfo c = calls[i];
735
736            if (c != null) {
737                DriverCall dc;
738
739                dc = c.toDriverCall(i + 1);
740                ret.add(dc);
741            }
742        }
743
744        Log.d("GSM", "SC< getDriverCalls " + ret);
745
746        return ret;
747    }
748
749    public List<String>
750    getClccLines() {
751        ArrayList<String> ret = new ArrayList<String>(calls.length);
752
753        for (int i = 0 ; i < calls.length ; i++) {
754            CallInfo c = calls[i];
755
756            if (c != null) {
757                ret.add((c.toCLCCLine(i + 1)));
758            }
759        }
760
761        return ret;
762    }
763
764    private int
765    countActiveLines() throws InvalidStateEx {
766        boolean hasMpty = false;
767        boolean hasHeld = false;
768        boolean hasActive = false;
769        boolean hasConnecting = false;
770        boolean hasRinging = false;
771        boolean mptyIsHeld = false;
772
773        for (int i = 0 ; i < calls.length ; i++) {
774            CallInfo call = calls[i];
775
776            if (call != null) {
777                if (!hasMpty && call.isMpty) {
778                    mptyIsHeld = call.state == CallInfo.State.HOLDING;
779                } else if (call.isMpty && mptyIsHeld
780                    && call.state == CallInfo.State.ACTIVE
781                ) {
782                    Log.e("ModelInterpreter", "Invalid state");
783                    throw new InvalidStateEx();
784                } else if (!call.isMpty && hasMpty && mptyIsHeld
785                    && call.state == CallInfo.State.HOLDING
786                ) {
787                    Log.e("ModelInterpreter", "Invalid state");
788                    throw new InvalidStateEx();
789                }
790
791                hasMpty |= call.isMpty;
792                hasHeld |= call.state == CallInfo.State.HOLDING;
793                hasActive |= call.state == CallInfo.State.ACTIVE;
794                hasConnecting |= call.isConnecting();
795                hasRinging |= call.isRinging();
796            }
797        }
798
799        int ret = 0;
800
801        if (hasHeld) ret++;
802        if (hasActive) ret++;
803        if (hasConnecting) ret++;
804        if (hasRinging) ret++;
805
806        return ret;
807    }
808
809}
810