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