auto import from //depot/cupcake/@135843
diff --git a/telephony/android_modem.c b/telephony/android_modem.c
new file mode 100644
index 0000000..79e93b2
--- /dev/null
+++ b/telephony/android_modem.c
@@ -0,0 +1,1870 @@
+/* Copyright (C) 2007-2008 The Android Open Source Project
+**
+** This software is licensed under the terms of the GNU General Public
+** License version 2, as published by the Free Software Foundation, and
+** may be copied, distributed, and modified under those terms.
+**
+** This program is distributed in the hope that it will be useful,
+** but WITHOUT ANY WARRANTY; without even the implied warranty of
+** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+** GNU General Public License for more details.
+*/
+#include "android/android.h"
+#include "android_modem.h"
+#include "android/utils/debug.h"
+#include "android/utils/timezone.h"
+#include "android/utils/system.h"
+#include "sim_card.h"
+#include "sysdeps.h"
+#include <memory.h>
+#include <stdarg.h>
+#include <time.h>
+#include <assert.h>
+#include <stdio.h>
+#include "sms.h"
+#include "remote_call.h"
+
+#define  DEBUG  1
+
+#if  1
+#  define  D_ACTIVE  VERBOSE_CHECK(modem)
+#else
+#  define  D_ACTIVE  DEBUG
+#endif
+
+#if 1
+#  define  R_ACTIVE  VERBOSE_CHECK(radio)
+#else
+#  define  R_ACTIVE  DEBUG
+#endif
+
+#if DEBUG
+#  define  D(...)   do { if (D_ACTIVE) fprintf( stderr, __VA_ARGS__ ); } while (0)
+#  define  R(...)   do { if (R_ACTIVE) fprintf( stderr, __VA_ARGS__ ); } while (0)
+#else
+#  define  D(...)   ((void)0)
+#  define  R(...)   ((void)0)
+#endif
+
+#define  CALL_DELAY_DIAL   1000
+#define  CALL_DELAY_ALERT  1000
+
+/* the Android GSM stack checks that the operator's name has changed
+ * when roaming is on. If not, it will not update the Roaming status icon
+ *
+ * this means that we need to emulate two distinct operators:
+ * - the first one for the 'home' registration state, must also correspond
+ *   to the emulated user's IMEI
+ *
+ * - the second one for the 'roaming' registration state, must have a
+ *   different name and MCC/MNC
+ */
+
+#define  OPERATOR_HOME_INDEX 0
+#define  OPERATOR_HOME_MCC   310
+#define  OPERATOR_HOME_MNC   260
+#define  OPERATOR_HOME_NAME  "Android"
+#define  OPERATOR_HOME_MCCMNC  STRINGIFY(OPERATOR_HOME_MCC) \
+                               STRINGIFY(OPERATOR_HOME_MNC)
+
+#define  OPERATOR_ROAMING_INDEX 1
+#define  OPERATOR_ROAMING_MCC   310
+#define  OPERATOR_ROAMING_MNC   295
+#define  OPERATOR_ROAMING_NAME  "TelKila"
+#define  OPERATOR_ROAMING_MCCMNC  STRINGIFY(OPERATOR_ROAMING_MCC) \
+                                  STRINGIFY(OPERATOR_ROAMING_MNC)
+
+#if DEBUG
+static const char*  quote( const char*  line )
+{
+    static char  temp[1024];
+    const char*  hexdigits = "0123456789abcdef";
+    char*        p = temp;
+    int          c;
+
+    while ((c = *line++) != 0) {
+        c &= 255;
+        if (c >= 32 && c < 127) {
+            *p++ = c;
+        }
+        else if (c == '\r') {
+            memcpy( p, "<CR>", 4 );
+            p += 4;
+        }
+        else if (c == '\n') {
+            memcpy( p, "<LF>", 4 );strcat( p, "<LF>" );
+            p += 4;
+        }
+        else {
+            p[0] = '\\';
+            p[1] = 'x';
+            p[2] = hexdigits[ (c) >> 4 ];
+            p[3] = hexdigits[ (c) & 15 ];
+            p += 4;
+        }
+    }
+    *p = 0;
+    return temp;
+}
+#endif
+
+extern AGprsNetworkType
+android_parse_network_type( const char*  speed )
+{
+    const struct { const char* name; AGprsNetworkType  type; }  types[] = {
+         { "gprs", A_GPRS_NETWORK_GPRS },
+         { "edge", A_GPRS_NETWORK_EDGE },
+         { "umts", A_GPRS_NETWORK_UMTS },
+         { "hsdpa", A_GPRS_NETWORK_UMTS },  /* not handled yet by Android GSM framework */
+         { "full", A_GPRS_NETWORK_UMTS },
+         { NULL, 0 }
+    };
+    int  nn;
+
+    for (nn = 0; types[nn].name; nn++) {
+        if ( !strcmp(speed, types[nn].name) )
+            return types[nn].type;
+    }
+    /* not found, be conservative */
+    return A_GPRS_NETWORK_GPRS;
+}
+
+/* 'mode' for +CREG/+CGREG commands */
+typedef enum {
+    A_REGISTRATION_UNSOL_DISABLED     = 0,
+    A_REGISTRATION_UNSOL_ENABLED      = 1,
+    A_REGISTRATION_UNSOL_ENABLED_FULL = 2
+} ARegistrationUnsolMode;
+
+/* Operator selection mode, see +COPS commands */
+typedef enum {
+    A_SELECTION_AUTOMATIC,
+    A_SELECTION_MANUAL,
+    A_SELECTION_DEREGISTRATION,
+    A_SELECTION_SET_FORMAT,
+    A_SELECTION_MANUAL_AUTOMATIC
+} AOperatorSelection;
+
+/* Operator status, see +COPS commands */
+typedef enum {
+    A_STATUS_UNKNOWN = 0,
+    A_STATUS_AVAILABLE,
+    A_STATUS_CURRENT,
+    A_STATUS_DENIED
+} AOperatorStatus;
+
+typedef struct {
+    AOperatorStatus  status;
+    char             name[3][16];
+} AOperatorRec, *AOperator;
+
+typedef struct AVoiceCallRec {
+    ACallRec    call;
+    SysTimer    timer;
+    AModem      modem;
+    char        is_remote;
+} AVoiceCallRec, *AVoiceCall;
+
+#define  MAX_OPERATORS  4
+
+typedef enum {
+    A_DATA_IP = 0,
+    A_DATA_PPP
+} ADataType;
+
+#define  A_DATA_APN_SIZE  32
+
+typedef struct {
+    int        id;
+    int        active;
+    ADataType  type;
+    char       apn[ A_DATA_APN_SIZE ];
+
+} ADataContextRec, *ADataContext;
+
+/* the spec says that there can only be a max of 4 contexts */
+#define  MAX_DATA_CONTEXTS  4
+#define  MAX_CALLS          4
+
+#define  A_MODEM_SELF_SIZE   3
+
+typedef struct AModemRec_
+{
+    /* Radio state */
+    ARadioState   radio_state;
+    int           area_code;
+    int           cell_id;
+    int           base_port;
+
+    /* SMS */
+    int           wait_sms;
+
+    /* SIM card */
+    ASimCard      sim;
+
+    /* voice and data network registration */
+    ARegistrationUnsolMode   voice_mode;
+    ARegistrationState       voice_state;
+    ARegistrationUnsolMode   data_mode;
+    ARegistrationState       data_state;
+    AGprsNetworkType         data_network;
+
+    /* operator names */
+    AOperatorSelection  oper_selection_mode;
+    ANameIndex          oper_name_index;
+    int                 oper_index;
+    int                 oper_count;
+    AOperatorRec        operators[ MAX_OPERATORS ];
+
+    /* data connection contexts */
+    ADataContextRec     data_contexts[ MAX_DATA_CONTEXTS ];
+
+    /* active calls */
+    AVoiceCallRec       calls[ MAX_CALLS ];
+    int                 call_count;
+
+    /* unsolicited callback */  /* XXX: TODO: use this */
+    AModemUnsolFunc     unsol_func;
+    void*               unsol_opaque;
+
+    SmsReceiver         sms_receiver;
+
+    int                 out_size;
+    char                out_buff[1024];
+
+} AModemRec;
+
+
+static void
+amodem_unsol( AModem  modem, const char* format, ... )
+{
+    if (modem->unsol_func) {
+        va_list  args;
+        va_start(args, format);
+        vsnprintf( modem->out_buff, sizeof(modem->out_buff), format, args );
+        va_end(args);
+
+        modem->unsol_func( modem->unsol_opaque, modem->out_buff );
+    }
+}
+
+void
+amodem_receive_sms( AModem  modem, SmsPDU  sms )
+{
+#define  SMS_UNSOL_HEADER  "+CMT: 0\r\n"
+
+    if (modem->unsol_func) {
+        int    len, max;
+        char*  p;
+
+        strcpy( modem->out_buff, SMS_UNSOL_HEADER );
+        p   = modem->out_buff + (sizeof(SMS_UNSOL_HEADER)-1);
+        max = sizeof(modem->out_buff) - 3 - (sizeof(SMS_UNSOL_HEADER)-1);
+        len = smspdu_to_hex( sms, p, max );
+        if (len > max) /* too long */
+            return;
+        p[len]   = '\r';
+        p[len+1] = '\n';
+        p[len+2] = 0;
+
+        R( "SMS>> %s\n", p );
+
+        modem->unsol_func( modem->unsol_opaque, modem->out_buff );
+    }
+}
+
+static const char*
+amodem_printf( AModem  modem, const char*  format, ... )
+{
+    va_list  args;
+    va_start(args, format);
+    vsnprintf( modem->out_buff, sizeof(modem->out_buff), format, args );
+    va_end(args);
+
+    return modem->out_buff;
+}
+
+static void
+amodem_begin_line( AModem  modem )
+{
+    modem->out_size = 0;
+}
+
+static void
+amodem_add_line( AModem  modem, const char*  format, ... )
+{
+    va_list  args;
+    va_start(args, format);
+    modem->out_size += vsnprintf( modem->out_buff + modem->out_size,
+                                  sizeof(modem->out_buff) - modem->out_size,
+                                  format, args );
+    va_end(args);
+}
+
+static const char*
+amodem_end_line( AModem  modem )
+{
+    modem->out_buff[ modem->out_size ] = 0;
+    return modem->out_buff;
+}
+
+static void
+amodem_reset( AModem  modem )
+{
+    modem->radio_state = A_RADIO_STATE_OFF;
+    modem->wait_sms    = 0;
+
+    modem->oper_name_index     = 2;
+    modem->oper_selection_mode = A_SELECTION_AUTOMATIC;
+    modem->oper_index          = 0;
+    modem->oper_count          = 2;
+
+    modem->area_code = -1;
+    modem->cell_id   = -1;
+
+    strcpy( modem->operators[0].name[0], OPERATOR_HOME_NAME );
+    strcpy( modem->operators[0].name[1], OPERATOR_HOME_NAME );
+    strcpy( modem->operators[0].name[2], OPERATOR_HOME_MCCMNC );
+
+    modem->operators[0].status        = A_STATUS_AVAILABLE;
+
+    strcpy( modem->operators[1].name[0], OPERATOR_ROAMING_NAME );
+    strcpy( modem->operators[1].name[1], OPERATOR_ROAMING_NAME );
+    strcpy( modem->operators[1].name[2], OPERATOR_ROAMING_MCCMNC );
+
+    modem->operators[1].status        = A_STATUS_AVAILABLE;
+
+    modem->voice_mode   = A_REGISTRATION_UNSOL_ENABLED_FULL;
+    modem->voice_state  = A_REGISTRATION_HOME;
+    modem->data_mode    = A_REGISTRATION_UNSOL_ENABLED_FULL;
+    modem->data_state   = A_REGISTRATION_HOME;
+    modem->data_network = A_GPRS_NETWORK_UMTS;
+}
+
+static AModemRec   _android_modem[1];
+
+AModem
+amodem_create( int  base_port, AModemUnsolFunc  unsol_func, void*  unsol_opaque )
+{
+    AModem  modem = _android_modem;
+
+    amodem_reset( modem );
+    modem->base_port    = base_port;
+    modem->unsol_func   = unsol_func;
+    modem->unsol_opaque = unsol_opaque;
+
+    modem->sim = asimcard_create();
+
+    return  modem;
+}
+
+void
+amodem_destroy( AModem  modem )
+{
+    asimcard_destroy( modem->sim );
+    modem->sim = NULL;
+}
+
+
+static int
+amodem_has_network( AModem  modem )
+{
+    return !(modem->radio_state == A_RADIO_STATE_OFF   ||
+             modem->oper_index < 0                  ||
+             modem->oper_index >= modem->oper_count ||
+             modem->oper_selection_mode == A_SELECTION_DEREGISTRATION );
+}
+
+
+ARadioState
+amodem_get_radio_state( AModem modem )
+{
+    return modem->radio_state;
+}
+
+void
+amodem_set_radio_state( AModem modem, ARadioState  state )
+{
+    modem->radio_state = state;
+}
+
+ASimCard
+amodem_get_sim( AModem  modem )
+{
+    return  modem->sim;
+}
+
+ARegistrationState
+amodem_get_voice_registration( AModem  modem )
+{
+    return modem->voice_state;
+}
+
+void
+amodem_set_voice_registration( AModem  modem, ARegistrationState  state )
+{
+    modem->voice_state = state;
+
+    if (state == A_REGISTRATION_HOME)
+        modem->oper_index = OPERATOR_HOME_INDEX;
+    else if (state == A_REGISTRATION_ROAMING)
+        modem->oper_index = OPERATOR_ROAMING_INDEX;
+
+    switch (modem->voice_mode) {
+        case A_REGISTRATION_UNSOL_ENABLED:
+            amodem_unsol( modem, "+CREG: %d,%d\r",
+                          modem->voice_mode, modem->voice_state );
+            break;
+
+        case A_REGISTRATION_UNSOL_ENABLED_FULL:
+            amodem_unsol( modem, "+CREG: %d,%d, \"%04x\", \"%04x\"\r",
+                          modem->voice_mode, modem->voice_state,
+                          modem->area_code, modem->cell_id );
+            break;
+        default:
+            ;
+    }
+}
+
+ARegistrationState
+amodem_get_data_registration( AModem  modem )
+{
+    return modem->data_state;
+}
+
+void
+amodem_set_data_registration( AModem  modem, ARegistrationState  state )
+{
+    modem->data_state = state;
+
+    switch (modem->data_mode) {
+        case A_REGISTRATION_UNSOL_ENABLED:
+            amodem_unsol( modem, "+CGREG: %d,%d\r",
+                          modem->data_mode, modem->data_state );
+            break;
+
+        case A_REGISTRATION_UNSOL_ENABLED_FULL:
+            amodem_unsol( modem, "+CGREG: %d,%d,\"%04x\",\"%04x\",\"%04x\"\r",
+                          modem->data_mode, modem->data_state,
+                          modem->area_code, modem->cell_id,
+                          modem->data_network );
+            break;
+
+        default:
+            ;
+    }
+}
+
+void
+amodem_set_data_network_type( AModem  modem, AGprsNetworkType   type )
+{
+    modem->data_network = type;
+    amodem_set_data_registration( modem, modem->data_state );
+}
+
+int
+amodem_get_operator_name ( AModem  modem, ANameIndex  index, char*  buffer, int  buffer_size )
+{
+    AOperator  oper;
+    int        len;
+
+    if ( (unsigned)modem->oper_index >= (unsigned)modem->oper_count ||
+         (unsigned)index > 2 )
+        return 0;
+
+    oper = modem->operators + modem->oper_index;
+    len  = strlen(oper->name[index]) + 1;
+
+    if (buffer_size > len)
+        buffer_size = len;
+
+    if (buffer_size > 0) {
+        memcpy( buffer, oper->name[index], buffer_size-1 );
+        buffer[buffer_size] = 0;
+    }
+    return len;
+}
+
+/* reset one operator name from a user-provided buffer, set buffer_size to -1 for zero-terminated strings */
+void
+amodem_set_operator_name( AModem  modem, ANameIndex  index, const char*  buffer, int  buffer_size )
+{
+    AOperator  oper;
+    int        avail;
+
+    if ( (unsigned)modem->oper_index >= (unsigned)modem->oper_count ||
+         (unsigned)index > 2 )
+        return;
+
+    oper = modem->operators + modem->oper_index;
+
+    avail = sizeof(oper->name[0]);
+    if (buffer_size < 0)
+        buffer_size = strlen(buffer);
+    if (buffer_size > avail-1)
+        buffer_size = avail-1;
+    memcpy( oper->name[index], buffer, buffer_size );
+    oper->name[index][buffer_size] = 0;
+}
+
+/** CALLS
+ **/
+int
+amodem_get_call_count( AModem  modem )
+{
+    return modem->call_count;
+}
+
+ACall
+amodem_get_call( AModem  modem, int  index )
+{
+    if ((unsigned)index >= (unsigned)modem->call_count)
+        return NULL;
+
+    return &modem->calls[index].call;
+}
+
+static AVoiceCall
+amodem_alloc_call( AModem   modem )
+{
+    AVoiceCall  call  = NULL;
+    int         count = modem->call_count;
+
+    if (count < MAX_CALLS) {
+        int  id;
+
+        /* find a valid id for this call */
+        for (id = 0; id < modem->call_count; id++) {
+            int  found = 0;
+            int  nn;
+            for (nn = 0; nn < count; nn++) {
+                if ( modem->calls[nn].call.id == (id+1) ) {
+                    found = 1;
+                    break;
+                }
+            }
+            if (!found)
+                break;
+        }
+        call          = modem->calls + count;
+        call->call.id = id + 1;
+        call->modem   = modem;
+
+        modem->call_count += 1;
+    }
+    return call;
+}
+
+
+static void
+amodem_free_call( AModem  modem, AVoiceCall  call )
+{
+    int  nn;
+
+    if (call->timer) {
+        sys_timer_destroy( call->timer );
+        call->timer = NULL;
+    }
+
+    if (call->is_remote) {
+        remote_call_cancel( call->call.number, modem->base_port );
+        call->is_remote = 0;
+    }
+
+    for (nn = 0; nn < modem->call_count; nn++) {
+        if ( modem->calls + nn == call )
+            break;
+    }
+    assert( nn < modem->call_count );
+
+    memmove( modem->calls + nn,
+             modem->calls + nn + 1,
+             (modem->call_count - 1 - nn)*sizeof(*call) );
+
+    modem->call_count -= 1;
+}
+
+
+static AVoiceCall
+amodem_find_call( AModem  modem, int  id )
+{
+    int  nn;
+
+    for (nn = 0; nn < modem->call_count; nn++) {
+        AVoiceCall call = modem->calls + nn;
+        if (call->call.id == id)
+            return call;
+    }
+    return NULL;
+}
+
+static void
+amodem_send_calls_update( AModem  modem )
+{
+   /* despite its name, this really tells the system that the call
+    * state has changed */
+    amodem_unsol( modem, "RING\r" );
+}
+
+
+int
+amodem_add_inbound_call( AModem  modem, const char*  number )
+{
+    AVoiceCall  vcall = amodem_alloc_call( modem );
+    ACall       call  = &vcall->call;
+    int         len;
+
+    if (call == NULL)
+        return -1;
+
+    call->dir   = A_CALL_INBOUND;
+    call->state = A_CALL_INCOMING;
+    call->mode  = A_CALL_VOICE;
+    call->multi = 0;
+
+    vcall->is_remote = (remote_number_string_to_port(number) > 0);
+
+    len  = strlen(number);
+    if (len >= sizeof(call->number))
+        len = sizeof(call->number)-1;
+
+    memcpy( call->number, number, len );
+    call->number[len] = 0;
+
+    amodem_send_calls_update( modem );
+    return 0;
+}
+
+ACall
+amodem_find_call_by_number( AModem  modem, const char*  number )
+{
+    AVoiceCall  vcall = modem->calls;
+    AVoiceCall  vend  = vcall + modem->call_count;
+
+    if (!number)
+        return NULL;
+
+    for ( ; vcall < vend; vcall++ )
+        if ( !strcmp(vcall->call.number, number) )
+            return &vcall->call;
+
+    return  NULL;
+}
+
+
+static void
+acall_set_state( AVoiceCall    call, ACallState  state )
+{
+    if (state != call->call.state)
+    {
+        if (call->is_remote)
+        {
+            const char*  number = call->call.number;
+            int          port   = call->modem->base_port;
+
+            switch (state) {
+                case A_CALL_HELD:
+                    remote_call_other( number, port, REMOTE_CALL_HOLD );
+                    break;
+
+                case A_CALL_ACTIVE:
+                    remote_call_other( number, port, REMOTE_CALL_ACCEPT );
+                    break;
+
+                default: ;
+            }
+        }
+        call->call.state = state;
+    }
+}
+
+
+int
+amodem_update_call( AModem  modem, const char*  fromNumber, ACallState  state )
+{
+    AVoiceCall  vcall = (AVoiceCall) amodem_find_call_by_number(modem, fromNumber);
+
+    if (vcall == NULL)
+        return -1;
+
+    acall_set_state( vcall, state );
+    amodem_send_calls_update(modem);
+    return 0;
+}
+
+
+int
+amodem_disconnect_call( AModem  modem, const char*  number )
+{
+    AVoiceCall  vcall = (AVoiceCall) amodem_find_call_by_number(modem, number);
+
+    if (!vcall)
+        return -1;
+
+    amodem_free_call( modem, vcall );
+    amodem_send_calls_update(modem);
+    return 0;
+}
+
+/** COMMAND HANDLERS
+ **/
+
+static const char*
+unknownCommand( const char*  cmd, AModem  modem )
+{
+    modem=modem;
+    fprintf(stderr, ">>> unknown command '%s'\n", cmd );
+    return "ERROR: unknown command\r";
+}
+
+static const char*
+handleRadioPower( const char*  cmd, AModem  modem )
+{
+    if ( !strcmp( cmd, "+CFUN=0" ) )
+    {
+        /* turn radio off */
+        modem->radio_state = A_RADIO_STATE_OFF;
+    }
+    else if ( !strcmp( cmd, "+CFUN=1" ) )
+    {
+        /* turn radio on */
+        modem->radio_state = A_RADIO_STATE_ON;
+    }
+    return NULL;
+}
+
+static const char*
+handleRadioPowerReq( const char*  cmd, AModem  modem )
+{
+    if (modem->radio_state != A_RADIO_STATE_OFF)
+        return "+CFUN=1";
+    else
+        return "+CFUN=0";
+}
+
+static const char*
+handleSIMStatusReq( const char*  cmd, AModem  modem )
+{
+    const char*  answer = NULL;
+
+    switch (asimcard_get_status(modem->sim)) {
+        case A_SIM_STATUS_ABSENT:    answer = "+CPIN: ABSENT"; break;
+        case A_SIM_STATUS_READY:     answer = "+CPIN: READY"; break;
+        case A_SIM_STATUS_NOT_READY: answer = "+CMERROR: NOT READY"; break;
+        case A_SIM_STATUS_PIN:       answer = "+CPIN: SIM PIN"; break;
+        case A_SIM_STATUS_PUK:       answer = "+CPIN: SIM PUK"; break;
+        case A_SIM_STATUS_NETWORK_PERSONALIZATION: answer = "+CPIN: PH-NET PIN"; break;
+        default:
+            answer = "ERROR: internal error";
+    }
+    return answer;
+}
+
+static const char*
+handleNetworkRegistration( const char*  cmd, AModem  modem )
+{
+    if ( !memcmp( cmd, "+CREG", 5 ) ) {
+        cmd += 5;
+        if (cmd[0] == '?') {
+            return amodem_printf( modem, "+CREG: %d,%d, \"%04x\", \"%04x\"",
+                                  modem->voice_mode, modem->voice_state,
+                                  modem->area_code, modem->cell_id );
+        } else if (cmd[0] == '=') {
+            switch (cmd[1]) {
+                case '0':
+                    modem->voice_mode  = A_REGISTRATION_UNSOL_DISABLED;
+                    break;
+
+                case '1':
+                    modem->voice_mode  = A_REGISTRATION_UNSOL_ENABLED;
+                    break;
+
+                case '2':
+                    modem->voice_mode = A_REGISTRATION_UNSOL_ENABLED_FULL;
+                    break;
+
+                case '?':
+                    return "+CREG: (0-2)";
+
+                default:
+                    return "ERROR: BAD COMMAND";
+            }
+        } else {
+            assert( 0 && "unreachable" );
+        }
+    } else if ( !memcmp( cmd, "+CGREG", 6 ) ) {
+        cmd += 6;
+        if (cmd[0] == '?') {\
+            return amodem_printf( modem, "+CGREG: %d,%d,\"%04x\",\"%04x\",\"%04x\"",
+                                  modem->data_mode, modem->data_state,
+                                  modem->area_code, modem->cell_id,
+                                  modem->data_network );
+        } else if (cmd[0] == '=') {
+            switch (cmd[1]) {
+                case '0':
+                    modem->data_mode  = A_REGISTRATION_UNSOL_DISABLED;
+                    break;
+
+                case '1':
+                    modem->data_mode  = A_REGISTRATION_UNSOL_ENABLED;
+                    break;
+
+                case '2':
+                    modem->data_mode = A_REGISTRATION_UNSOL_ENABLED_FULL;
+                    break;
+
+                case '?':
+                    return "+CGREG: (0-2)";
+
+                default:
+                    return "ERROR: BAD COMMAND";
+            }
+        } else {
+            assert( 0 && "unreachable" );
+        }
+    }
+    return NULL;
+}
+
+static const char*
+handleSetDialTone( const char*  cmd, AModem  modem )
+{
+    /* XXX: TODO */
+    return NULL;
+}
+
+static const char*
+handleDeleteSMSonSIM( const char*  cmd, AModem  modem )
+{
+    /* XXX: TODO */
+    return NULL;
+}
+
+static const char*
+handleSIM_IO( const char*  cmd, AModem  modem )
+{
+    return asimcard_io( modem->sim, cmd );
+}
+
+
+static const char*
+handleOperatorSelection( const char*  cmd, AModem  modem )
+{
+    assert( !memcmp( "+COPS", cmd, 5 ) );
+    cmd += 5;
+    if (cmd[0] == '?') { /* ask for current operator */
+        AOperator  oper = &modem->operators[ modem->oper_index ];
+
+        if ( !amodem_has_network( modem ) )
+        {
+            /* this error code means "no network" */
+            return amodem_printf( modem, "+CME ERROR: 30" );
+        }
+
+        oper = &modem->operators[ modem->oper_index ];
+
+        if ( modem->oper_name_index == 2 )
+            return amodem_printf( modem, "+COPS: %d,2,%s",
+                                  modem->oper_selection_mode,
+                                  oper->name[2] );
+
+        return amodem_printf( modem, "+COPS: %d,%d,\"%s\"",
+                              modem->oper_selection_mode,
+                              modem->oper_name_index,
+                              oper->name[ modem->oper_name_index ] );
+    }
+    else if (cmd[0] == '=' && cmd[1] == '?') {  /* ask for all available operators */
+        const char*  comma = "+COPS: ";
+        int          nn;
+        amodem_begin_line( modem );
+        for (nn = 0; nn < modem->oper_count; nn++) {
+            AOperator  oper = &modem->operators[nn];
+            amodem_add_line( modem, "%s(%d,\"%s\",\"%s\",\"%s\")", comma,
+                             oper->status, oper->name[0], oper->name[1], oper->name[2] );
+            comma = ", ";
+        }
+        return amodem_end_line( modem );
+    }
+    else if (cmd[0] == '=') {
+        switch (cmd[1]) {
+            case '0':
+                modem->oper_selection_mode = A_SELECTION_AUTOMATIC;
+                return NULL;
+
+            case '1':
+                {
+                    int  format, nn, len, found = -1;
+
+                    if (cmd[2] != ',')
+                        goto BadCommand;
+                    format = cmd[3] - '0';
+                    if ( (unsigned)format > 2 )
+                        goto BadCommand;
+                    if (cmd[4] != ',')
+                        goto BadCommand;
+                    cmd += 5;
+                    len  = strlen(cmd);
+                    if (*cmd == '"') {
+                        cmd++;
+                        len -= 2;
+                    }
+                    if (len <= 0)
+                        goto BadCommand;
+
+                    for (nn = 0; nn < modem->oper_count; nn++) {
+                        AOperator    oper = modem->operators + nn;
+                        char*        name = oper->name[ format ];
+
+                        if ( !memcpy( name, cmd, len ) && name[len] == 0 ) {
+                            found = nn;
+                            break;
+                        }
+                    }
+
+                    if (found < 0) {
+                        /* Selection failed */
+                        return "+CME ERROR: 529";
+                    } else if (modem->operators[found].status == A_STATUS_DENIED) {
+                        /* network not allowed */
+                        return "+CME ERROR: 32";
+                    }
+                    modem->oper_index = found;
+
+                    /* set the voice and data registration states to home or roaming
+                     * depending on the operator index
+                     */
+                    if (found == OPERATOR_HOME_INDEX) {
+                        modem->voice_state = A_REGISTRATION_HOME;
+                        modem->data_state  = A_REGISTRATION_HOME;
+                    } else if (found == OPERATOR_ROAMING_INDEX) {
+                        modem->voice_state = A_REGISTRATION_ROAMING;
+                        modem->data_state  = A_REGISTRATION_ROAMING;
+                    }
+                    return NULL;
+                }
+
+            case '2':
+                modem->oper_selection_mode = A_SELECTION_DEREGISTRATION;
+                return NULL;
+
+            case '3':
+                {
+                    int format;
+
+                    if (cmd[2] != ',')
+                        goto BadCommand;
+
+                    format = cmd[3] - '0';
+                    if ( (unsigned)format > 2 )
+                        goto BadCommand;
+
+                    modem->oper_name_index = format;
+                    return NULL;
+                }
+            default:
+                ;
+        }
+    }
+BadCommand:
+    return unknownCommand(cmd,modem);
+}
+
+static const char*
+handleRequestOperator( const char*  cmd, AModem  modem )
+{
+    AOperator  oper;
+    cmd=cmd;
+
+    if ( !amodem_has_network(modem) )
+        return "+CME ERROR: 30";
+
+    oper = modem->operators + modem->oper_index;
+    modem->oper_name_index = 2;
+    return amodem_printf( modem, "+COPS: 0,0,\"%s\"\r"
+                          "+COPS: 0,1,\"%s\"\r"
+                          "+COPS: 0,2,\"%s\"",
+                          oper->name[0], oper->name[1], oper->name[2] );
+}
+
+static const char*
+handleSendSMStoSIM( const char*  cmd, AModem  modem )
+{
+    /* XXX: TODO */
+    return "ERROR: unimplemented";
+}
+
+static const char*
+handleSendSMS( const char*  cmd, AModem  modem )
+{
+    modem->wait_sms = 1;
+    return "> ";
+}
+
+#if 0
+static void
+sms_address_dump( SmsAddress  address, FILE*  out )
+{
+    int  nn, len = address->len;
+
+    if (address->toa == 0x91) {
+        fprintf( out, "+" );
+    }
+    for (nn = 0; nn < len; nn += 2)
+    {
+        static const char  dialdigits[16] = "0123456789*#,N%";
+        int  c = address->data[nn/2];
+
+        fprintf( out, "%c", dialdigits[c & 0xf] );
+        if (nn+1 >= len)
+            break;
+
+        fprintf( out, "%c", dialdigits[(c >> 4) & 0xf] );
+    }
+}
+
+static void
+smspdu_dump( SmsPDU  pdu, FILE*  out )
+{
+    SmsAddressRec    address;
+    unsigned char    temp[256];
+    int              len;
+
+    if (pdu == NULL) {
+        fprintf( out, "SMS PDU is (null)\n" );
+        return;
+    }
+
+    fprintf( out, "SMS PDU type:       " );
+    switch (smspdu_get_type(pdu)) {
+        case SMS_PDU_DELIVER: fprintf(out, "DELIVER"); break;
+        case SMS_PDU_SUBMIT:  fprintf(out, "SUBMIT"); break;
+        case SMS_PDU_STATUS_REPORT: fprintf(out, "STATUS_REPORT"); break;
+        default: fprintf(out, "UNKNOWN");
+    }
+    fprintf( out, "\n        sender:   " );
+    if (smspdu_get_sender_address(pdu, &address) < 0)
+        fprintf( out, "(N/A)" );
+    else
+        sms_address_dump(&address, out);
+    fprintf( out, "\n        receiver: " );
+    if (smspdu_get_receiver_address(pdu, &address) < 0)
+        fprintf(out, "(N/A)");
+    else
+        sms_address_dump(&address, out);
+    fprintf( out, "\n        text:     " );
+    len = smspdu_get_text_message( pdu, temp, sizeof(temp)-1 );
+    if (len > sizeof(temp)-1 )
+        len = sizeof(temp)-1;
+    fprintf( out, "'%.*s'\n", len, temp );
+}
+#endif
+
+static const char*
+handleSendSMSText( const char*  cmd, AModem  modem )
+{
+#if 1
+    SmsAddressRec  address;
+    char           number[16];
+    int            numlen;
+    int            len = strlen(cmd);
+    SmsPDU         pdu;
+
+    /* get rid of trailing escape */
+    if (len > 0 && cmd[len-1] == 0x1a)
+        len -= 1;
+
+    pdu = smspdu_create_from_hex( cmd, len );
+    if (pdu == NULL) {
+        D("%s: invalid SMS PDU ?: '%s'\n", __FUNCTION__, cmd);
+        return "+CMS ERROR: INVALID SMS PDU";
+    }
+    if (smspdu_get_receiver_address(pdu, &address) < 0) {
+        D("%s: could not get SMS receiver address from '%s'\n",
+          __FUNCTION__, cmd);
+        return "+CMS ERROR: BAD SMS RECEIVER ADDRESS";
+    }
+
+    do {
+        int  index;
+
+        numlen = sms_address_to_str( &address, number, sizeof(number) );
+        if (numlen > sizeof(number)-1)
+            break;
+
+        number[numlen] = 0;
+        if ( remote_number_string_to_port( number ) < 0 )
+            break;
+
+        if (modem->sms_receiver == NULL) {
+            modem->sms_receiver = sms_receiver_create();
+            if (modem->sms_receiver == NULL) {
+                D( "%s: could not create SMS receiver\n", __FUNCTION__ );
+                break;
+            }
+        }
+
+        index = sms_receiver_add_submit_pdu( modem->sms_receiver, pdu );
+        if (index < 0) {
+            D( "%s: could not add submit PDU\n", __FUNCTION__ );
+            break;
+        }
+        /* the PDU is now owned by the receiver */
+        pdu = NULL;
+
+        if (index > 0) {
+            SmsAddressRec  from[1];
+            char           temp[10];
+            SmsPDU*        deliver;
+            int            nn;
+
+            sprintf( temp, "%d", modem->base_port );
+            sms_address_from_str( from, temp, strlen(temp) );
+
+            deliver = sms_receiver_create_deliver( modem->sms_receiver, index, from );
+            if (deliver == NULL) {
+                D( "%s: could not create deliver PDUs for SMS index %d\n",
+                   __FUNCTION__, index );
+                break;
+            }
+
+            for (nn = 0; deliver[nn] != NULL; nn++) {
+                if ( remote_call_sms( number, modem->base_port, deliver[nn] ) < 0 ) {
+                    D( "%s: could not send SMS PDU to remote emulator\n",
+                       __FUNCTION__ );
+                    break;
+                }
+            }
+
+            smspdu_free_list(deliver);
+        }
+
+    } while (0);
+
+    if (pdu != NULL)
+        smspdu_free(pdu);
+
+#elif 1
+    SmsAddressRec  address;
+    char           number[16];
+    int            numlen;
+    int            len = strlen(cmd);
+    SmsPDU         pdu;
+
+    /* get rid of trailing escape */
+    if (len > 0 && cmd[len-1] == 0x1a)
+        len -= 1;
+
+    pdu = smspdu_create_from_hex( cmd, len );
+    if (pdu == NULL) {
+        D("%s: invalid SMS PDU ?: '%s'\n", __FUNCTION__, cmd);
+        return "+CMS ERROR: INVALID SMS PDU";
+    }
+    if (smspdu_get_receiver_address(pdu, &address) < 0) {
+        D("%s: could not get SMS receiver address from '%s'\n",
+          __FUNCTION__, cmd);
+        return "+CMS ERROR: BAD SMS RECEIVER ADDRESS";
+    }
+    do {
+        numlen = sms_address_to_str( &address, number, sizeof(number) );
+        if (numlen > sizeof(number)-1)
+            break;
+
+        number[numlen] = 0;
+        if ( remote_number_string_to_port( number ) < 0 )
+            break;
+
+        if ( remote_call_sms( number, modem->base_port, pdu ) < 0 )
+        {
+            D("%s: could not send SMS PDU to remote emulator\n",
+              __FUNCTION__);
+            return "+CMS ERROR: NO EMULATOR RECEIVER";
+        }
+    } while (0);
+#else
+    fprintf(stderr, "SMS<< %s\n", cmd);
+    SmsPDU  pdu = smspdu_create_from_hex( cmd, strlen(cmd) );
+    if (pdu == NULL) {
+        fprintf(stderr, "invalid SMS PDU ?: '%s'\n", cmd);
+    } else {
+        smspdu_dump(pdu, stderr);
+    }
+#endif
+    return "+CMGS: 0\rOK\r";
+}
+
+static const char*
+handleChangeOrEnterPIN( const char*  cmd, AModem  modem )
+{
+    assert( !memcmp( cmd, "+CPIN=", 6 ) );
+    cmd += 6;
+
+    switch (asimcard_get_status(modem->sim)) {
+        case A_SIM_STATUS_ABSENT:
+            return "+CME ERROR: SIM ABSENT";
+
+        case A_SIM_STATUS_NOT_READY:
+            return "+CME ERROR: SIM NOT READY";
+
+        case A_SIM_STATUS_READY:
+            /* this may be a request to change the PIN */
+            {
+                if (strlen(cmd) == 9 && cmd[4] == ',') {
+                    char  pin[5];
+                    memcpy( pin, cmd, 4 ); pin[4] = 0;
+
+                    if ( !asimcard_check_pin( modem->sim, pin ) )
+                        return "+CME ERROR: BAD PIN";
+
+                    memcpy( pin, cmd+5, 4 );
+                    asimcard_set_pin( modem->sim, pin );
+                    return "+CPIN: READY";
+                }
+            }
+            break;
+
+        case A_SIM_STATUS_PIN:   /* waiting for PIN */
+            if ( asimcard_check_pin( modem->sim, cmd ) )
+                return "+CPIN: READY";
+            else
+                return "+CME ERROR: BAD PIN";
+
+        case A_SIM_STATUS_PUK:
+            if (strlen(cmd) == 9 && cmd[4] == ',') {
+                char  puk[5];
+                memcpy( puk, cmd, 4 );
+                puk[4] = 0;
+                if ( asimcard_check_puk( modem->sim, puk, cmd+5 ) )
+                    return "+CPIN: READY";
+                else
+                    return "+CME ERROR: BAD PUK";
+            }
+            return "+CME ERROR: BAD PUK";
+
+        default:
+            return "+CPIN: PH-NET PIN";
+    }
+
+    return "+CME ERROR: BAD FORMAT";
+}
+
+
+static const char*
+handleListCurrentCalls( const char*  cmd, AModem  modem )
+{
+    int  nn;
+    amodem_begin_line( modem );
+    for (nn = 0; nn < modem->call_count; nn++) {
+        AVoiceCall  vcall = modem->calls + nn;
+        ACall       call  = &vcall->call;
+        if (call->mode == A_CALL_VOICE)
+            amodem_add_line( modem, "+CLCC: %d,%d,%d,%d,%d,\"%s\",%d\r\n",
+                             call->id, call->dir, call->state, call->mode,
+                             call->multi, call->number, 129 );
+    }
+    return amodem_end_line( modem );
+}
+
+/* retrieve the current time and zone in a format suitable
+ * for %CTZV: unsolicited message
+ *  "yy/mm/dd,hh:mm:ss(+/-)tz"
+ *   mm is 0-based
+ *   tz is in number of quarter-hours
+ *
+ * it seems reference-ril doesn't parse the comma (,) as anything else than a token
+ * separator, so use a column (:) instead, the Java parsing code won't see a difference
+ *
+ */
+static const char*
+handleEndOfInit( const char*  cmd, AModem  modem )
+{
+    time_t       now = time(NULL);
+    struct tm    utc, local;
+    long         e_local, e_utc;
+    long         tzdiff;
+    char         tzname[64];
+
+    tzset();
+
+    utc   = *gmtime( &now );
+    local = *localtime( &now );
+
+    e_local = local.tm_min + 60*(local.tm_hour + 24*local.tm_yday);
+    e_utc   = utc.tm_min   + 60*(utc.tm_hour   + 24*utc.tm_yday);
+
+    if ( utc.tm_year < local.tm_year )
+        e_local += 24*60;
+    else if ( utc.tm_year > local.tm_year )
+        e_utc += 24*60;
+
+    tzdiff = e_local - e_utc;  /* timezone offset in minutes */
+
+   /* retrieve a zoneinfo-compatible name for the host timezone
+    */
+    {
+        char*  end = tzname + sizeof(tzname);
+        char*  p = bufprint_zoneinfo_timezone( tzname, end );
+        if (p >= end)
+            strcpy(tzname, "Unknown/Unknown");
+
+        /* now replace every / in the timezone name by a "!"
+         * that's because the code that reads the CTZV line is
+         * dumb and treats a / as a field separator...
+         */
+        p = tzname;
+        while (1) {
+            p = strchr(p, '/');
+            if (p == NULL)
+                break;
+            *p = '!';
+            p += 1;
+        }
+    }
+
+   /* as a special extension, we append the name of the host's time zone to the
+    * string returned with %CTZ. the system should contain special code to detect
+    * and deal with this case (since it normally relied on the operator's country code
+    * which is hard to simulate on a general-purpose computer
+    */
+    return amodem_printf( modem, "%%CTZV: %02d/%02d/%02d:%02d:%02d:%02d%c%d:%d:%s",
+             (utc.tm_year + 1900) % 100, utc.tm_mon + 1, utc.tm_mday, utc.tm_hour, utc.tm_min, utc.tm_sec,
+             (tzdiff >= 0) ? '+' : '-', (tzdiff >= 0 ? tzdiff : -tzdiff) / 15,
+             (local.tm_isdst > 0),
+             tzname );
+}
+
+
+static const char*
+handleListPDPContexts( const char*  cmd, AModem  modem )
+{
+    int  nn;
+    assert( !memcmp( cmd, "+CGACT?", 7 ) );
+    amodem_begin_line( modem );
+    for (nn = 0; nn < MAX_DATA_CONTEXTS; nn++) {
+        ADataContext  data = modem->data_contexts + nn;
+        if (!data->active)
+            continue;
+        amodem_add_line( modem, "+CGACT: %d,%d\r", data->id, data->active );
+    }
+    return amodem_end_line( modem );
+}
+
+static const char*
+handleDefinePDPContext( const char*  cmd, AModem  modem )
+{
+    assert( !memcmp( cmd, "+CGDCONT=", 9 ) );
+    cmd += 9;
+    if (cmd[0] == '?') {
+        int  nn;
+        amodem_begin_line(modem);
+        for (nn = 0; nn < MAX_DATA_CONTEXTS; nn++) {
+            ADataContext  data = modem->data_contexts + nn;
+            if (!data->active)
+                continue;
+            amodem_add_line( modem, "+CGDCONT: %d,%s,\"%s\",,0,0\r\n",
+                             data->id,
+                             data->type == A_DATA_IP ? "IP" : "PPP",
+                             data->apn );
+        }
+        return amodem_end_line(modem);
+    } else {
+        /* template is +CGDCONT=<id>,"<type>","<apn>",,0,0 */
+        int              id = cmd[0] - '1';
+        ADataType        type;
+        char             apn[32];
+        ADataContext     data;
+
+        if ((unsigned)id > 3)
+            goto BadCommand;
+
+        if ( !memcmp( cmd+1, ",\"IP\",\"", 7 ) ) {
+            type = A_DATA_IP;
+            cmd += 8;
+        } else if ( !memcmp( cmd+1, ",\"PPP\",\"", 8 ) ) {
+            type = A_DATA_PPP;
+            cmd += 9;
+        } else
+            goto BadCommand;
+
+        {
+            const char*  p = strchr( cmd, '"' );
+            int          len;
+            if (p == NULL)
+                goto BadCommand;
+            len = (int)( p - cmd );
+            if (len > sizeof(apn)-1 )
+                len = sizeof(apn)-1;
+            memcpy( apn, cmd, len );
+            apn[len] = 0;
+        }
+
+        data = modem->data_contexts + id;
+
+        data->id     = id + 1;
+        data->active = 1;
+        data->type   = type;
+        memcpy( data->apn, apn, sizeof(data->apn) );
+    }
+    return NULL;
+BadCommand:
+    return "ERROR: BAD COMMAND";
+}
+
+
+static const char*
+handleStartPDPContext( const char*  cmd, AModem  modem )
+{
+    /* XXX: TODO: handle PDP start appropriately */
+    /* for the moment, always return success */
+#if 0
+    AVoiceCall  vcall = amodem_alloc_call( modem );
+    ACall       call  = (ACall) vcall;
+    if (call == NULL) {
+        return "ERROR: TOO MANY CALLS";
+    }
+    call->id    = 1;
+    call->dir   = A_CALL_OUTBOUND;
+    /* XXX: it would be better to delay this */
+    call->state = A_CALL_ACTIVE;
+    call->mode  = A_CALL_DATA;
+    call->multi = 0;
+    strcpy( call->number, "012345" );
+#endif
+    return NULL;
+}
+
+
+static void
+remote_voice_call_event( void*  _vcall, int  success )
+{
+    AVoiceCall  vcall = _vcall;
+    AModem      modem = vcall->modem;
+
+    /* NOTE: success only means we could send the "gsm in new" command
+     * to the remote emulator, nothing more */
+
+    if (!success) {
+        /* aargh, the remote emulator probably quitted at that point */
+        amodem_free_call(modem, vcall);
+        amodem_send_calls_update(modem);
+    }
+}
+
+
+static void
+voice_call_event( void*  _vcall )
+{
+    AVoiceCall  vcall = _vcall;
+    ACall       call  = &vcall->call;
+
+    switch (call->state) {
+        case A_CALL_DIALING:
+            call->state = A_CALL_ALERTING;
+
+            if (vcall->is_remote) {
+                if ( remote_call_dial( call->number,
+                                       vcall->modem->base_port,
+                                       remote_voice_call_event, vcall ) < 0 )
+                {
+                   /* we could not connect, probably because the corresponding
+                    * emulator is not running, so simply destroy this call.
+                    * XXX: should we send some sort of message to indicate BAD NUMBER ? */
+                    /* it seems the Android code simply waits for changes in the list   */
+                    amodem_free_call( vcall->modem, vcall );
+                }
+            } else {
+               /* this is not a remote emulator number, so just simulate
+                * a small ringing delay */
+                sys_timer_set( vcall->timer, sys_time_ms() + CALL_DELAY_ALERT,
+                               voice_call_event, vcall );
+            }
+            break;
+
+        case A_CALL_ALERTING:
+            call->state = A_CALL_ACTIVE;
+            break;
+
+        default:
+            assert( 0 && "unreachable event call state" );
+    }
+    amodem_send_calls_update(vcall->modem);
+}
+
+
+static const char*
+handleDial( const char*  cmd, AModem  modem )
+{
+    AVoiceCall  vcall = amodem_alloc_call( modem );
+    ACall       call  = &vcall->call;
+    int         len;
+
+    if (call == NULL)
+        return "ERROR: TOO MANY CALLS";
+
+    assert( cmd[0] == 'D' );
+    call->dir   = A_CALL_OUTBOUND;
+    call->state = A_CALL_DIALING;
+    call->mode  = A_CALL_VOICE;
+    call->multi = 0;
+
+    cmd += 1;
+    len  = strlen(cmd);
+    if (len > 0 && cmd[len-1] == ';')
+        len--;
+    if (len >= sizeof(call->number))
+        len = sizeof(call->number)-1;
+
+    memcpy( call->number, cmd, len );
+    call->number[len] = 0;
+
+    vcall->is_remote = (remote_number_string_to_port(call->number) > 0);
+
+    vcall->timer = sys_timer_create();
+    sys_timer_set( vcall->timer, sys_time_ms() + CALL_DELAY_DIAL,
+                   voice_call_event, vcall );
+
+    return NULL;
+}
+
+
+static const char*
+handleAnswer( const char*  cmd, AModem  modem )
+{
+    int  nn;
+    for (nn = 0; nn < modem->call_count; nn++) {
+        AVoiceCall  vcall = modem->calls + nn;
+        ACall       call  = &vcall->call;
+
+        if (cmd[0] == 'A') {
+            if (call->state == A_CALL_INCOMING) {
+                acall_set_state( vcall, A_CALL_ACTIVE );
+            }
+            else if (call->state == A_CALL_ACTIVE) {
+                acall_set_state( vcall, A_CALL_HELD );
+            }
+        } else if (cmd[0] == 'H') {
+            /* ATH: hangup, since user is busy */
+            if (call->state == A_CALL_INCOMING) {
+                amodem_free_call( modem, vcall );
+                break;
+            }
+        }
+    }
+    return NULL;
+}
+
+static const char*
+handleHangup( const char*  cmd, AModem  modem )
+{
+    if ( !memcmp(cmd, "+CHLD=", 6) ) {
+        int  nn;
+        cmd += 6;
+        switch (cmd[0]) {
+            case '0':  /* release all held, and set busy for waiting calls */
+                for (nn = 0; nn < modem->call_count; nn++) {
+                    AVoiceCall  vcall = modem->calls + nn;
+                    ACall       call  = &vcall->call;
+                    if (call->mode != A_CALL_VOICE)
+                        continue;
+                    if (call->state == A_CALL_HELD    ||
+                        call->state == A_CALL_WAITING ||
+                        call->state == A_CALL_INCOMING) {
+                        amodem_free_call(modem, vcall);
+                        nn--;
+                    }
+                }
+                break;
+
+            case '1':
+                if (cmd[1] == 0) { /* release all active, accept held one */
+                    for (nn = 0; nn < modem->call_count; nn++) {
+                        AVoiceCall  vcall = modem->calls + nn;
+                        ACall       call  = &vcall->call;
+                        if (call->mode != A_CALL_VOICE)
+                            continue;
+                        if (call->state == A_CALL_ACTIVE) {
+                            amodem_free_call(modem, vcall);
+                            nn--;
+                        }
+                        else if (call->state == A_CALL_HELD     ||
+                                 call->state == A_CALL_WAITING) {
+                            acall_set_state( vcall, A_CALL_ACTIVE );
+                        }
+                    }
+                } else {  /* release specific call */
+                    int  id = cmd[1] - '0';
+                    AVoiceCall  vcall = amodem_find_call( modem, id );
+                    if (vcall != NULL)
+                        amodem_free_call( modem, vcall );
+                }
+                break;
+
+            case '2':
+                if (cmd[1] == 0) {  /* place all active on hold, accept held or waiting one */
+                    for (nn = 0; nn < modem->call_count; nn++) {
+                        AVoiceCall  vcall = modem->calls + nn;
+                        ACall       call  = &vcall->call;
+                        if (call->mode != A_CALL_VOICE)
+                            continue;
+                        if (call->state == A_CALL_ACTIVE) {
+                            acall_set_state( vcall, A_CALL_HELD );
+                        }
+                        else if (call->state == A_CALL_HELD     ||
+                                 call->state == A_CALL_WAITING) {
+                            acall_set_state( vcall, A_CALL_ACTIVE );
+                        }
+                    }
+                } else {  /* place all active on hold, except a specific one */
+                    int   id = cmd[1] - '0';
+                    for (nn = 0; nn < modem->call_count; nn++) {
+                        AVoiceCall  vcall = modem->calls + nn;
+                        ACall       call  = &vcall->call;
+                        if (call->mode != A_CALL_VOICE)
+                            continue;
+                        if (call->state == A_CALL_ACTIVE && call->id != id) {
+                            acall_set_state( vcall, A_CALL_HELD );
+                        }
+                    }
+                }
+                break;
+
+            case '3':  /* add a held call to the conversation */
+                for (nn = 0; nn < modem->call_count; nn++) {
+                    AVoiceCall  vcall = modem->calls + nn;
+                    ACall       call  = &vcall->call;
+                    if (call->mode != A_CALL_VOICE)
+                        continue;
+                    if (call->state == A_CALL_HELD) {
+                        acall_set_state( vcall, A_CALL_ACTIVE );
+                        break;
+                    }
+                }
+                break;
+
+            case '4':  /* connect the two calls */
+                for (nn = 0; nn < modem->call_count; nn++) {
+                    AVoiceCall  vcall = modem->calls + nn;
+                    ACall       call  = &vcall->call;
+                    if (call->mode != A_CALL_VOICE)
+                        continue;
+                    if (call->state == A_CALL_HELD) {
+                        acall_set_state( vcall, A_CALL_ACTIVE );
+                        break;
+                    }
+                }
+                break;
+        }
+    }
+    else
+        return "ERROR: BAD COMMAND";
+
+    return NULL;
+}
+
+
+/* a function used to deal with a non-trivial request */
+typedef const char*  (*ResponseHandler)(const char*  cmd, AModem  modem);
+
+static const struct {
+    const char*      cmd;     /* command coming from libreference-ril.so, if first
+                                 character is '!', then the rest is a prefix only */
+
+    const char*      answer;  /* default answer, NULL if needs specific handling or
+                                 if OK is good enough */
+
+    ResponseHandler  handler; /* specific handler, ignored if 'answer' is not NULL,
+                                 NULL if OK is good enough */
+} sDefaultResponses[] =
+{
+    /* see onRadioPowerOn() */
+    { "%CPHS=1", NULL, NULL },
+    { "%CTZV=1", NULL, NULL },
+
+    /* see onSIMReady() */
+    { "+CSMS=1", "+CSMS: 1, 1, 1", NULL },
+    { "+CNMI=1,2,2,1,1", NULL, NULL },
+
+    /* see requestRadioPower() */
+    { "+CFUN=0", NULL, handleRadioPower },
+    { "+CFUN=1", NULL, handleRadioPower },
+
+    /* see requestOrSendPDPContextList() */
+    { "+CGACT?", "", handleListPDPContexts },
+
+    /* see requestOperator() */
+    { "+COPS=3,0;+COPS?;+COPS=3,1;+COPS?;+COPS=3,2;+COPS?", NULL, handleRequestOperator },
+
+    /* see requestQueryNetworkSelectionMode() */
+    { "!+COPS", NULL, handleOperatorSelection },
+
+    /* see requestGetCurrentCalls() */
+    { "+CLCC", NULL, handleListCurrentCalls },
+
+    /* see requestWriteSmsToSim() */
+    { "!+CMGW=", NULL, handleSendSMStoSIM },
+
+    /* see requestHangup() */
+    { "!+CHLD=", NULL, handleHangup },
+
+    /* see requestSignalStrength() */
+    { "+CSQ", "+CSQ: 7,99", NULL },  /* XXX: TODO: implement variable signal strength and error rates */
+
+    /* see requestRegistrationState() */
+    { "!+CREG", NULL, handleNetworkRegistration },
+    { "!+CGREG", NULL, handleNetworkRegistration },
+
+    /* see requestSendSMS() */
+    { "!+CMGS=", NULL, handleSendSMS },
+
+    /* see requestSetupDefaultPDP() */
+    { "%CPRIM=\"GMM\",\"CONFIG MULTISLOT_CLASS=<10>\"", NULL, NULL },
+    { "%DATA=2,\"UART\",1,,\"SER\",\"UART\",0", NULL, NULL },
+
+    { "!+CGDCONT=", NULL, handleDefinePDPContext },
+
+    { "+CGQREQ=1", NULL, NULL },
+    { "+CGQMIN=1", NULL, NULL },
+    { "+CGEREP=1,0", NULL, NULL },
+    { "+CGACT=1,0", NULL, NULL },
+    { "D*99***1#", NULL, handleStartPDPContext },
+
+    /* see requestDial() */
+    { "!D", NULL, handleDial },  /* the code says that success/error is ignored, the call state will
+                              be polled through +CLCC instead */
+
+    /* see requestSMSAcknowledge() */
+    { "+CNMA=1", NULL, NULL },
+    { "+CNMA=2", NULL, NULL },
+
+    /* see requestSIM_IO() */
+    { "!+CRSM=", NULL, handleSIM_IO },
+
+    /* see onRequest() */
+    { "+CHLD=0", NULL, handleHangup },
+    { "+CHLD=1", NULL, handleHangup },
+    { "+CHLD=2", NULL, handleHangup },
+    { "+CHLD=3", NULL, handleHangup },
+    { "A", NULL, handleAnswer },  /* answer the call */
+    { "H", NULL, handleAnswer },  /* user is busy */
+    { "!+VTS=", NULL, handleSetDialTone },
+    { "+CIMI", OPERATOR_HOME_MCCMNC "000000000", NULL },   /* request internation subscriber identification number */
+    { "+CGSN", "000000000000000", NULL },   /* request model version */
+    { "+CUSD=2",NULL, NULL }, /* Cancel USSD */
+    { "+COPS=0", NULL, handleOperatorSelection }, /* set network selection to automatic */
+    { "!+CMGD=", NULL, handleDeleteSMSonSIM }, /* delete SMS on SIM */
+    { "!+CPIN=", NULL, handleChangeOrEnterPIN },
+
+    /* see getSIMStatus() */
+    { "+CPIN?", NULL, handleSIMStatusReq },
+    { "+CNMI?", "+CNMI: 1,2,2,1,1", NULL },
+
+    /* see isRadioOn() */
+    { "+CFUN?", NULL, handleRadioPowerReq },
+
+    /* see initializeCallback() */
+    { "E0Q0V1", NULL, NULL },
+    { "S0=0", NULL, NULL },
+    { "+CMEE=1", NULL, NULL },
+    { "+CREG=2", NULL, handleNetworkRegistration },
+    { "+CREG=1", NULL, handleNetworkRegistration },
+    { "+CGREG=1", NULL, handleNetworkRegistration },
+    { "+CCWA=1", NULL, NULL },
+    { "+CMOD=0", NULL, NULL },
+    { "+CMUT=0", NULL, NULL },
+    { "+CSSN=0,1", NULL, NULL },
+    { "+COLP=0", NULL, NULL },
+    { "+CSCS=\"HEX\"", NULL, NULL },
+    { "+CUSD=1", NULL, NULL },
+    { "+CGEREP=1,0", NULL, NULL },
+    { "+CMGF=0", NULL, handleEndOfInit },  /* now is a goof time to send the current tme and timezone */
+    { "%CPI=3", NULL, NULL },
+    { "%CSTAT=1", NULL, NULL },
+
+    /* end of list */
+    {NULL, NULL, NULL}
+};
+
+
+#define  REPLY(str)  do { const char*  s = (str); R(">> %s\n", quote(s)); return s; } while (0)
+
+const char*  amodem_send( AModem  modem, const char*  cmd )
+{
+    const char*  answer;
+
+    if ( modem->wait_sms != 0 ) {
+        modem->wait_sms = 0;
+        R( "SMS<< %s\n", quote(cmd) );
+        answer = handleSendSMSText( cmd, modem );
+        REPLY(answer);
+    }
+
+    /* everything that doesn't start with 'AT' is not a command, right ? */
+    if ( cmd[0] != 'A' || cmd[1] != 'T' || cmd[2] == 0 ) {
+        /* R( "-- %s\n", quote(cmd) ); */
+        return NULL;
+    }
+    R( "<< %s\n", quote(cmd) );
+
+    cmd += 2;
+
+    /* TODO: implement command handling */
+    {
+        int  nn, found = 0;
+
+        for (nn = 0; ; nn++) {
+            const char*  scmd = sDefaultResponses[nn].cmd;
+
+            if (!scmd) /* end of list */
+                break;
+
+            if (scmd[0] == '!') { /* prefix match */
+                int  len = strlen(++scmd);
+
+                if ( !memcmp( scmd, cmd, len ) ) {
+                    found = 1;
+                    break;
+                }
+            } else { /* full match */
+                if ( !strcmp( scmd, cmd ) ) {
+                    found = 1;
+                    break;
+                }
+            }
+        }
+
+        if ( !found )
+        {
+            D( "** UNSUPPORTED COMMAND **\n" );
+            REPLY( "ERROR: UNSUPPORTED" );
+        }
+        else
+        {
+            const char*      answer  = sDefaultResponses[nn].answer;
+            ResponseHandler  handler = sDefaultResponses[nn].handler;
+
+            if ( answer != NULL ) {
+                REPLY( amodem_printf( modem, "%s\rOK", answer ) );
+            }
+
+            if (handler == NULL) {
+                REPLY( "OK" );
+            }
+
+            answer = handler( cmd, modem );
+            if (answer == NULL)
+                REPLY( "OK" );
+
+            if ( !memcmp( answer, "> ", 2 )     ||
+                 !memcmp( answer, "ERROR", 5 )  ||
+                 !memcmp( answer, "+CME ERROR", 6 ) )
+            {
+                REPLY( answer );
+            }
+
+            if (answer != modem->out_buff)
+                REPLY( amodem_printf( modem, "%s\rOK", answer ) );
+
+            strcat( modem->out_buff, "\rOK" );
+            REPLY( answer );
+        }
+    }
+}