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 
17 package com.android.internal.telephony.test;
18 
19 import android.os.Looper;
20 import android.os.Message;
21 import android.os.Handler;
22 import android.telephony.PhoneNumberUtils;
23 import com.android.internal.telephony.ATParseEx;
24 import com.android.internal.telephony.DriverCall;
25 import java.util.List;
26 import java.util.ArrayList;
27 
28 import android.telephony.Rlog;
29 
30 class 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 
State(int value)39         State(int value) {mValue = value;}
40 
41         private final int mValue;
value()42         public int value() {return mValue;}
43     }
44 
45     boolean mIsMT;
46     State mState;
47     boolean mIsMpty;
48     String mNumber;
49     int mTOA;
50 
CallInfo(boolean isMT, State state, boolean isMpty, String number)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
createOutgoingCall(String number)65     createOutgoingCall(String number) {
66         return new CallInfo (false, State.DIALING, false, number);
67     }
68 
69     static CallInfo
createIncomingCall(String number)70     createIncomingCall(String number) {
71         return new CallInfo (true, State.INCOMING, false, number);
72     }
73 
74     String
toCLCCLine(int index)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
toDriverCall(int index)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
isActiveOrHeld()109     isActiveOrHeld() {
110         return mState == State.ACTIVE || mState == State.HOLDING;
111     }
112 
113     boolean
isConnecting()114     isConnecting() {
115         return mState == State.DIALING || mState == State.ALERTING;
116     }
117 
118     boolean
isRinging()119     isRinging() {
120         return mState == State.INCOMING || mState == State.WAITING;
121     }
122 
123 }
124 
125 class InvalidStateEx extends Exception {
InvalidStateEx()126     InvalidStateEx() {
127 
128     }
129 }
130 
131 
132 class 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 
SimulatedGsmCallState(Looper looper)154     public SimulatedGsmCallState(Looper looper) {
155         super(looper);
156     }
157 
158     @Override
159     public void
handleMessage(Message msg)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
triggerRing(String number)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
progressConnectingCallState()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
progressConnectingToActive()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
setAutoProgressConnectingCall(boolean b)264     setAutoProgressConnectingCall(boolean b) {
265         mAutoProgressConnecting = b;
266     }
267 
268     public void
setNextDialFailImmediately(boolean b)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
triggerHangupForeground()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
triggerHangupBackground()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
triggerHangupAll()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
onAnswer()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
onHangup()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
onChld(char c0, char c1)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
releaseHeldOrUDUB()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
releaseActiveAcceptHeldOrWaiting()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
switchActiveAndHeldOrWaiting()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
separateCall(int index)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
conference()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
explicitCallTransfer()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
onDial(String address)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>
getDriverCalls()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>
getClccLines()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
countActiveLines()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