Home | History | Annotate | Download | only in test
      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.util.Log;
     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 
     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 
    125 class InvalidStateEx extends Exception {
    126     InvalidStateEx() {
    127 
    128     }
    129 }
    130 
    131 
    132 class 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