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