The Android Open Source Project | 8b23a6c | 2009-03-03 19:30:32 -0800 | [diff] [blame] | 1 | /* Copyright (C) 2007-2008 The Android Open Source Project |
| 2 | ** |
| 3 | ** This software is licensed under the terms of the GNU General Public |
| 4 | ** License version 2, as published by the Free Software Foundation, and |
| 5 | ** may be copied, distributed, and modified under those terms. |
| 6 | ** |
| 7 | ** This program is distributed in the hope that it will be useful, |
| 8 | ** but WITHOUT ANY WARRANTY; without even the implied warranty of |
| 9 | ** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
| 10 | ** GNU General Public License for more details. |
| 11 | */ |
| 12 | #include "android/android.h" |
| 13 | #include "android_modem.h" |
David 'Digit' Turner | 73dd5fc | 2014-02-04 12:50:55 +0100 | [diff] [blame] | 14 | #include "android/config-file.h" |
Tim Baverstock | 622b8f4 | 2010-12-07 11:36:59 +0000 | [diff] [blame] | 15 | #include "android/config/config.h" |
| 16 | #include "android/snapshot.h" |
The Android Open Source Project | 8b23a6c | 2009-03-03 19:30:32 -0800 | [diff] [blame] | 17 | #include "android/utils/debug.h" |
| 18 | #include "android/utils/timezone.h" |
| 19 | #include "android/utils/system.h" |
Jaime Lopez | 1a00085 | 2010-07-21 18:03:58 -0700 | [diff] [blame] | 20 | #include "android/utils/bufprint.h" |
| 21 | #include "android/utils/path.h" |
Tim Baverstock | e50d724 | 2010-12-21 19:20:04 +0000 | [diff] [blame] | 22 | #include "hw/hw.h" |
| 23 | #include "qemu-common.h" |
The Android Open Source Project | 8b23a6c | 2009-03-03 19:30:32 -0800 | [diff] [blame] | 24 | #include "sim_card.h" |
| 25 | #include "sysdeps.h" |
| 26 | #include <memory.h> |
| 27 | #include <stdarg.h> |
| 28 | #include <time.h> |
| 29 | #include <assert.h> |
| 30 | #include <stdio.h> |
| 31 | #include "sms.h" |
| 32 | #include "remote_call.h" |
| 33 | |
| 34 | #define DEBUG 1 |
| 35 | |
| 36 | #if 1 |
| 37 | # define D_ACTIVE VERBOSE_CHECK(modem) |
| 38 | #else |
| 39 | # define D_ACTIVE DEBUG |
| 40 | #endif |
| 41 | |
| 42 | #if 1 |
| 43 | # define R_ACTIVE VERBOSE_CHECK(radio) |
| 44 | #else |
| 45 | # define R_ACTIVE DEBUG |
| 46 | #endif |
| 47 | |
| 48 | #if DEBUG |
| 49 | # define D(...) do { if (D_ACTIVE) fprintf( stderr, __VA_ARGS__ ); } while (0) |
| 50 | # define R(...) do { if (R_ACTIVE) fprintf( stderr, __VA_ARGS__ ); } while (0) |
| 51 | #else |
| 52 | # define D(...) ((void)0) |
| 53 | # define R(...) ((void)0) |
| 54 | #endif |
| 55 | |
| 56 | #define CALL_DELAY_DIAL 1000 |
| 57 | #define CALL_DELAY_ALERT 1000 |
| 58 | |
| 59 | /* the Android GSM stack checks that the operator's name has changed |
| 60 | * when roaming is on. If not, it will not update the Roaming status icon |
| 61 | * |
| 62 | * this means that we need to emulate two distinct operators: |
| 63 | * - the first one for the 'home' registration state, must also correspond |
| 64 | * to the emulated user's IMEI |
| 65 | * |
| 66 | * - the second one for the 'roaming' registration state, must have a |
| 67 | * different name and MCC/MNC |
| 68 | */ |
| 69 | |
| 70 | #define OPERATOR_HOME_INDEX 0 |
| 71 | #define OPERATOR_HOME_MCC 310 |
| 72 | #define OPERATOR_HOME_MNC 260 |
| 73 | #define OPERATOR_HOME_NAME "Android" |
| 74 | #define OPERATOR_HOME_MCCMNC STRINGIFY(OPERATOR_HOME_MCC) \ |
| 75 | STRINGIFY(OPERATOR_HOME_MNC) |
| 76 | |
| 77 | #define OPERATOR_ROAMING_INDEX 1 |
| 78 | #define OPERATOR_ROAMING_MCC 310 |
| 79 | #define OPERATOR_ROAMING_MNC 295 |
| 80 | #define OPERATOR_ROAMING_NAME "TelKila" |
| 81 | #define OPERATOR_ROAMING_MCCMNC STRINGIFY(OPERATOR_ROAMING_MCC) \ |
| 82 | STRINGIFY(OPERATOR_ROAMING_MNC) |
| 83 | |
Jaime Lopez | 1a00085 | 2010-07-21 18:03:58 -0700 | [diff] [blame] | 84 | static const char* _amodem_switch_technology(AModem modem, AModemTech newtech, int32_t newpreferred); |
| 85 | static int _amodem_set_cdma_subscription_source( AModem modem, ACdmaSubscriptionSource ss); |
| 86 | static int _amodem_set_cdma_prl_version( AModem modem, int prlVersion); |
| 87 | |
The Android Open Source Project | 8b23a6c | 2009-03-03 19:30:32 -0800 | [diff] [blame] | 88 | #if DEBUG |
| 89 | static const char* quote( const char* line ) |
| 90 | { |
| 91 | static char temp[1024]; |
| 92 | const char* hexdigits = "0123456789abcdef"; |
| 93 | char* p = temp; |
| 94 | int c; |
| 95 | |
| 96 | while ((c = *line++) != 0) { |
| 97 | c &= 255; |
| 98 | if (c >= 32 && c < 127) { |
| 99 | *p++ = c; |
| 100 | } |
| 101 | else if (c == '\r') { |
| 102 | memcpy( p, "<CR>", 4 ); |
| 103 | p += 4; |
| 104 | } |
| 105 | else if (c == '\n') { |
| 106 | memcpy( p, "<LF>", 4 );strcat( p, "<LF>" ); |
| 107 | p += 4; |
| 108 | } |
| 109 | else { |
| 110 | p[0] = '\\'; |
| 111 | p[1] = 'x'; |
| 112 | p[2] = hexdigits[ (c) >> 4 ]; |
| 113 | p[3] = hexdigits[ (c) & 15 ]; |
| 114 | p += 4; |
| 115 | } |
| 116 | } |
| 117 | *p = 0; |
| 118 | return temp; |
| 119 | } |
| 120 | #endif |
| 121 | |
Jaime Lopez | 1a00085 | 2010-07-21 18:03:58 -0700 | [diff] [blame] | 122 | extern AModemTech |
| 123 | android_parse_modem_tech( const char * tech ) |
| 124 | { |
| 125 | const struct { const char* name; AModemTech tech; } techs[] = { |
| 126 | { "gsm", A_TECH_GSM }, |
| 127 | { "wcdma", A_TECH_WCDMA }, |
| 128 | { "cdma", A_TECH_CDMA }, |
| 129 | { "evdo", A_TECH_EVDO }, |
| 130 | { "lte", A_TECH_LTE }, |
| 131 | { NULL, 0 } |
| 132 | }; |
| 133 | int nn; |
| 134 | |
| 135 | for (nn = 0; techs[nn].name; nn++) { |
| 136 | if (!strcmp(tech, techs[nn].name)) |
| 137 | return techs[nn].tech; |
| 138 | } |
| 139 | /* not found */ |
| 140 | return A_TECH_UNKNOWN; |
| 141 | } |
| 142 | |
| 143 | extern ADataNetworkType |
The Android Open Source Project | 8b23a6c | 2009-03-03 19:30:32 -0800 | [diff] [blame] | 144 | android_parse_network_type( const char* speed ) |
| 145 | { |
Jaime Lopez | 1a00085 | 2010-07-21 18:03:58 -0700 | [diff] [blame] | 146 | const struct { const char* name; ADataNetworkType type; } types[] = { |
| 147 | { "gprs", A_DATA_NETWORK_GPRS }, |
| 148 | { "edge", A_DATA_NETWORK_EDGE }, |
| 149 | { "umts", A_DATA_NETWORK_UMTS }, |
| 150 | { "hsdpa", A_DATA_NETWORK_UMTS }, /* not handled yet by Android GSM framework */ |
| 151 | { "full", A_DATA_NETWORK_UMTS }, |
| 152 | { "lte", A_DATA_NETWORK_LTE }, |
| 153 | { "cdma", A_DATA_NETWORK_CDMA1X }, |
| 154 | { "evdo", A_DATA_NETWORK_EVDO }, |
| 155 | { NULL, 0 } |
The Android Open Source Project | 8b23a6c | 2009-03-03 19:30:32 -0800 | [diff] [blame] | 156 | }; |
| 157 | int nn; |
| 158 | |
| 159 | for (nn = 0; types[nn].name; nn++) { |
Jaime Lopez | 1a00085 | 2010-07-21 18:03:58 -0700 | [diff] [blame] | 160 | if (!strcmp(speed, types[nn].name)) |
The Android Open Source Project | 8b23a6c | 2009-03-03 19:30:32 -0800 | [diff] [blame] | 161 | return types[nn].type; |
| 162 | } |
| 163 | /* not found, be conservative */ |
Jaime Lopez | 1a00085 | 2010-07-21 18:03:58 -0700 | [diff] [blame] | 164 | return A_DATA_NETWORK_GPRS; |
The Android Open Source Project | 8b23a6c | 2009-03-03 19:30:32 -0800 | [diff] [blame] | 165 | } |
| 166 | |
| 167 | /* 'mode' for +CREG/+CGREG commands */ |
| 168 | typedef enum { |
| 169 | A_REGISTRATION_UNSOL_DISABLED = 0, |
| 170 | A_REGISTRATION_UNSOL_ENABLED = 1, |
| 171 | A_REGISTRATION_UNSOL_ENABLED_FULL = 2 |
| 172 | } ARegistrationUnsolMode; |
| 173 | |
| 174 | /* Operator selection mode, see +COPS commands */ |
| 175 | typedef enum { |
| 176 | A_SELECTION_AUTOMATIC, |
| 177 | A_SELECTION_MANUAL, |
| 178 | A_SELECTION_DEREGISTRATION, |
| 179 | A_SELECTION_SET_FORMAT, |
| 180 | A_SELECTION_MANUAL_AUTOMATIC |
| 181 | } AOperatorSelection; |
| 182 | |
| 183 | /* Operator status, see +COPS commands */ |
| 184 | typedef enum { |
| 185 | A_STATUS_UNKNOWN = 0, |
| 186 | A_STATUS_AVAILABLE, |
| 187 | A_STATUS_CURRENT, |
| 188 | A_STATUS_DENIED |
| 189 | } AOperatorStatus; |
| 190 | |
| 191 | typedef struct { |
| 192 | AOperatorStatus status; |
| 193 | char name[3][16]; |
| 194 | } AOperatorRec, *AOperator; |
| 195 | |
| 196 | typedef struct AVoiceCallRec { |
| 197 | ACallRec call; |
| 198 | SysTimer timer; |
| 199 | AModem modem; |
| 200 | char is_remote; |
| 201 | } AVoiceCallRec, *AVoiceCall; |
| 202 | |
| 203 | #define MAX_OPERATORS 4 |
| 204 | |
| 205 | typedef enum { |
| 206 | A_DATA_IP = 0, |
| 207 | A_DATA_PPP |
| 208 | } ADataType; |
| 209 | |
| 210 | #define A_DATA_APN_SIZE 32 |
| 211 | |
| 212 | typedef struct { |
| 213 | int id; |
| 214 | int active; |
| 215 | ADataType type; |
| 216 | char apn[ A_DATA_APN_SIZE ]; |
| 217 | |
| 218 | } ADataContextRec, *ADataContext; |
| 219 | |
| 220 | /* the spec says that there can only be a max of 4 contexts */ |
| 221 | #define MAX_DATA_CONTEXTS 4 |
| 222 | #define MAX_CALLS 4 |
Jaime Lopez | 1a00085 | 2010-07-21 18:03:58 -0700 | [diff] [blame] | 223 | #define MAX_EMERGENCY_NUMBERS 16 |
| 224 | |
The Android Open Source Project | 8b23a6c | 2009-03-03 19:30:32 -0800 | [diff] [blame] | 225 | |
| 226 | #define A_MODEM_SELF_SIZE 3 |
| 227 | |
Jaime Lopez | 1a00085 | 2010-07-21 18:03:58 -0700 | [diff] [blame] | 228 | |
The Android Open Source Project | 8b23a6c | 2009-03-03 19:30:32 -0800 | [diff] [blame] | 229 | typedef struct AModemRec_ |
| 230 | { |
David Turner | 6f290f2 | 2009-04-18 20:50:40 -0700 | [diff] [blame] | 231 | /* Legacy support */ |
| 232 | char supportsNetworkDataType; |
| 233 | |
The Android Open Source Project | 8b23a6c | 2009-03-03 19:30:32 -0800 | [diff] [blame] | 234 | /* Radio state */ |
| 235 | ARadioState radio_state; |
| 236 | int area_code; |
| 237 | int cell_id; |
| 238 | int base_port; |
| 239 | |
Tim Baverstock | 4c6b10a | 2010-12-15 17:31:13 +0000 | [diff] [blame] | 240 | int rssi; |
| 241 | int ber; |
| 242 | |
The Android Open Source Project | 8b23a6c | 2009-03-03 19:30:32 -0800 | [diff] [blame] | 243 | /* SMS */ |
| 244 | int wait_sms; |
| 245 | |
| 246 | /* SIM card */ |
| 247 | ASimCard sim; |
| 248 | |
| 249 | /* voice and data network registration */ |
| 250 | ARegistrationUnsolMode voice_mode; |
| 251 | ARegistrationState voice_state; |
| 252 | ARegistrationUnsolMode data_mode; |
| 253 | ARegistrationState data_state; |
Jaime Lopez | 1a00085 | 2010-07-21 18:03:58 -0700 | [diff] [blame] | 254 | ADataNetworkType data_network; |
The Android Open Source Project | 8b23a6c | 2009-03-03 19:30:32 -0800 | [diff] [blame] | 255 | |
| 256 | /* operator names */ |
| 257 | AOperatorSelection oper_selection_mode; |
| 258 | ANameIndex oper_name_index; |
| 259 | int oper_index; |
| 260 | int oper_count; |
| 261 | AOperatorRec operators[ MAX_OPERATORS ]; |
| 262 | |
| 263 | /* data connection contexts */ |
| 264 | ADataContextRec data_contexts[ MAX_DATA_CONTEXTS ]; |
| 265 | |
| 266 | /* active calls */ |
| 267 | AVoiceCallRec calls[ MAX_CALLS ]; |
| 268 | int call_count; |
| 269 | |
| 270 | /* unsolicited callback */ /* XXX: TODO: use this */ |
| 271 | AModemUnsolFunc unsol_func; |
| 272 | void* unsol_opaque; |
| 273 | |
| 274 | SmsReceiver sms_receiver; |
| 275 | |
| 276 | int out_size; |
| 277 | char out_buff[1024]; |
| 278 | |
Jaime Lopez | 1a00085 | 2010-07-21 18:03:58 -0700 | [diff] [blame] | 279 | /* |
| 280 | * Hold non-volatile ram configuration for modem |
| 281 | */ |
| 282 | AConfig *nvram_config; |
| 283 | char *nvram_config_filename; |
| 284 | |
| 285 | AModemTech technology; |
| 286 | /* |
| 287 | * This is are really 4 byte-sized prioritized masks. |
| 288 | * Byte order gives the priority for the specific bitmask. |
| 289 | * Each bit position in each of the masks is indexed by the different |
| 290 | * A_TECH_XXXX values. |
| 291 | * e.g. 0x01 means only GSM is set (bit index 0), whereas 0x0f |
| 292 | * means that GSM,WCDMA,CDMA and EVDO are set |
| 293 | */ |
| 294 | int32_t preferred_mask; |
| 295 | ACdmaSubscriptionSource subscription_source; |
| 296 | ACdmaRoamingPref roaming_pref; |
| 297 | int in_emergency_mode; |
| 298 | int prl_version; |
| 299 | |
| 300 | const char *emergency_numbers[MAX_EMERGENCY_NUMBERS]; |
The Android Open Source Project | 8b23a6c | 2009-03-03 19:30:32 -0800 | [diff] [blame] | 301 | } AModemRec; |
| 302 | |
| 303 | |
| 304 | static void |
| 305 | amodem_unsol( AModem modem, const char* format, ... ) |
| 306 | { |
| 307 | if (modem->unsol_func) { |
| 308 | va_list args; |
| 309 | va_start(args, format); |
| 310 | vsnprintf( modem->out_buff, sizeof(modem->out_buff), format, args ); |
| 311 | va_end(args); |
| 312 | |
| 313 | modem->unsol_func( modem->unsol_opaque, modem->out_buff ); |
| 314 | } |
| 315 | } |
| 316 | |
| 317 | void |
| 318 | amodem_receive_sms( AModem modem, SmsPDU sms ) |
| 319 | { |
| 320 | #define SMS_UNSOL_HEADER "+CMT: 0\r\n" |
| 321 | |
| 322 | if (modem->unsol_func) { |
| 323 | int len, max; |
| 324 | char* p; |
| 325 | |
| 326 | strcpy( modem->out_buff, SMS_UNSOL_HEADER ); |
| 327 | p = modem->out_buff + (sizeof(SMS_UNSOL_HEADER)-1); |
| 328 | max = sizeof(modem->out_buff) - 3 - (sizeof(SMS_UNSOL_HEADER)-1); |
| 329 | len = smspdu_to_hex( sms, p, max ); |
| 330 | if (len > max) /* too long */ |
| 331 | return; |
| 332 | p[len] = '\r'; |
| 333 | p[len+1] = '\n'; |
| 334 | p[len+2] = 0; |
| 335 | |
| 336 | R( "SMS>> %s\n", p ); |
| 337 | |
| 338 | modem->unsol_func( modem->unsol_opaque, modem->out_buff ); |
| 339 | } |
| 340 | } |
| 341 | |
| 342 | static const char* |
| 343 | amodem_printf( AModem modem, const char* format, ... ) |
| 344 | { |
| 345 | va_list args; |
| 346 | va_start(args, format); |
| 347 | vsnprintf( modem->out_buff, sizeof(modem->out_buff), format, args ); |
| 348 | va_end(args); |
| 349 | |
| 350 | return modem->out_buff; |
| 351 | } |
| 352 | |
| 353 | static void |
| 354 | amodem_begin_line( AModem modem ) |
| 355 | { |
| 356 | modem->out_size = 0; |
| 357 | } |
| 358 | |
| 359 | static void |
| 360 | amodem_add_line( AModem modem, const char* format, ... ) |
| 361 | { |
| 362 | va_list args; |
| 363 | va_start(args, format); |
| 364 | modem->out_size += vsnprintf( modem->out_buff + modem->out_size, |
| 365 | sizeof(modem->out_buff) - modem->out_size, |
| 366 | format, args ); |
| 367 | va_end(args); |
| 368 | } |
| 369 | |
| 370 | static const char* |
| 371 | amodem_end_line( AModem modem ) |
| 372 | { |
| 373 | modem->out_buff[ modem->out_size ] = 0; |
| 374 | return modem->out_buff; |
| 375 | } |
| 376 | |
Jaime Lopez | 1a00085 | 2010-07-21 18:03:58 -0700 | [diff] [blame] | 377 | #define NV_OPER_NAME_INDEX "oper_name_index" |
| 378 | #define NV_OPER_INDEX "oper_index" |
| 379 | #define NV_SELECTION_MODE "selection_mode" |
| 380 | #define NV_OPER_COUNT "oper_count" |
| 381 | #define NV_MODEM_TECHNOLOGY "modem_technology" |
| 382 | #define NV_PREFERRED_MODE "preferred_mode" |
| 383 | #define NV_CDMA_SUBSCRIPTION_SOURCE "cdma_subscription_source" |
| 384 | #define NV_CDMA_ROAMING_PREF "cdma_roaming_pref" |
| 385 | #define NV_IN_ECBM "in_ecbm" |
| 386 | #define NV_EMERGENCY_NUMBER_FMT "emergency_number_%d" |
| 387 | #define NV_PRL_VERSION "prl_version" |
| 388 | #define NV_SREGISTER "sregister" |
| 389 | |
| 390 | #define MAX_KEY_NAME 40 |
| 391 | |
| 392 | static AConfig * |
| 393 | amodem_load_nvram( AModem modem ) |
| 394 | { |
| 395 | AConfig* root = aconfig_node(NULL, NULL); |
| 396 | D("Using config file: %s\n", modem->nvram_config_filename); |
| 397 | if (aconfig_load_file(root, modem->nvram_config_filename)) { |
| 398 | D("Unable to load config\n"); |
| 399 | aconfig_set(root, NV_MODEM_TECHNOLOGY, "gsm"); |
| 400 | aconfig_save_file(root, modem->nvram_config_filename); |
| 401 | } |
| 402 | return root; |
| 403 | } |
| 404 | |
| 405 | static int |
| 406 | amodem_nvram_get_int( AModem modem, const char *nvname, int defval) |
| 407 | { |
| 408 | int value; |
| 409 | char strval[MAX_KEY_NAME + 1]; |
| 410 | char *newvalue; |
| 411 | |
| 412 | value = aconfig_int(modem->nvram_config, nvname, defval); |
| 413 | snprintf(strval, MAX_KEY_NAME, "%d", value); |
| 414 | D("Setting value of %s to %d (%s)",nvname, value, strval); |
| 415 | newvalue = strdup(strval); |
| 416 | if (!newvalue) { |
| 417 | newvalue = ""; |
| 418 | } |
| 419 | aconfig_set(modem->nvram_config, nvname, newvalue); |
| 420 | |
| 421 | return value; |
| 422 | } |
| 423 | |
| 424 | const char * |
| 425 | amodem_nvram_get_str( AModem modem, const char *nvname, const char *defval) |
| 426 | { |
| 427 | const char *value; |
| 428 | |
| 429 | value = aconfig_str(modem->nvram_config, nvname, defval); |
| 430 | |
| 431 | if (!value) { |
| 432 | if (!defval) |
| 433 | return NULL; |
| 434 | value = defval; |
| 435 | } |
| 436 | |
| 437 | aconfig_set(modem->nvram_config, nvname, value); |
| 438 | |
| 439 | return value; |
| 440 | } |
| 441 | |
| 442 | static ACdmaSubscriptionSource _amodem_get_cdma_subscription_source( AModem modem ) |
| 443 | { |
| 444 | int iss = -1; |
| 445 | iss = amodem_nvram_get_int( modem, NV_CDMA_SUBSCRIPTION_SOURCE, A_SUBSCRIPTION_RUIM ); |
| 446 | if (iss >= A_SUBSCRIPTION_UNKNOWN || iss < 0) { |
| 447 | iss = A_SUBSCRIPTION_RUIM; |
| 448 | } |
| 449 | |
| 450 | return iss; |
| 451 | } |
| 452 | |
| 453 | static ACdmaRoamingPref _amodem_get_cdma_roaming_preference( AModem modem ) |
| 454 | { |
| 455 | int rp = -1; |
| 456 | rp = amodem_nvram_get_int( modem, NV_CDMA_ROAMING_PREF, A_ROAMING_PREF_ANY ); |
| 457 | if (rp >= A_ROAMING_PREF_UNKNOWN || rp < 0) { |
| 458 | rp = A_ROAMING_PREF_ANY; |
| 459 | } |
| 460 | |
| 461 | return rp; |
| 462 | } |
| 463 | |
The Android Open Source Project | 8b23a6c | 2009-03-03 19:30:32 -0800 | [diff] [blame] | 464 | static void |
| 465 | amodem_reset( AModem modem ) |
| 466 | { |
Jaime Lopez | 1a00085 | 2010-07-21 18:03:58 -0700 | [diff] [blame] | 467 | const char *tmp; |
| 468 | int i; |
| 469 | modem->nvram_config = amodem_load_nvram(modem); |
The Android Open Source Project | 8b23a6c | 2009-03-03 19:30:32 -0800 | [diff] [blame] | 470 | modem->radio_state = A_RADIO_STATE_OFF; |
| 471 | modem->wait_sms = 0; |
| 472 | |
Tim Baverstock | 4c6b10a | 2010-12-15 17:31:13 +0000 | [diff] [blame] | 473 | modem->rssi= 7; // Two signal strength bars |
| 474 | modem->ber = 99; // Means 'unknown' |
| 475 | |
Jaime Lopez | 1a00085 | 2010-07-21 18:03:58 -0700 | [diff] [blame] | 476 | modem->oper_name_index = amodem_nvram_get_int(modem, NV_OPER_NAME_INDEX, 2); |
| 477 | modem->oper_selection_mode = amodem_nvram_get_int(modem, NV_SELECTION_MODE, A_SELECTION_AUTOMATIC); |
| 478 | modem->oper_index = amodem_nvram_get_int(modem, NV_OPER_INDEX, 0); |
| 479 | modem->oper_count = amodem_nvram_get_int(modem, NV_OPER_COUNT, 2); |
| 480 | modem->in_emergency_mode = amodem_nvram_get_int(modem, NV_IN_ECBM, 0); |
| 481 | modem->prl_version = amodem_nvram_get_int(modem, NV_PRL_VERSION, 0); |
| 482 | |
| 483 | modem->emergency_numbers[0] = "911"; |
| 484 | char key_name[MAX_KEY_NAME + 1]; |
| 485 | for (i = 1; i < MAX_EMERGENCY_NUMBERS; i++) { |
| 486 | snprintf(key_name,MAX_KEY_NAME, NV_EMERGENCY_NUMBER_FMT, i); |
| 487 | modem->emergency_numbers[i] = amodem_nvram_get_str(modem,key_name, NULL); |
| 488 | } |
The Android Open Source Project | 8b23a6c | 2009-03-03 19:30:32 -0800 | [diff] [blame] | 489 | |
| 490 | modem->area_code = -1; |
| 491 | modem->cell_id = -1; |
| 492 | |
| 493 | strcpy( modem->operators[0].name[0], OPERATOR_HOME_NAME ); |
| 494 | strcpy( modem->operators[0].name[1], OPERATOR_HOME_NAME ); |
| 495 | strcpy( modem->operators[0].name[2], OPERATOR_HOME_MCCMNC ); |
| 496 | |
| 497 | modem->operators[0].status = A_STATUS_AVAILABLE; |
| 498 | |
| 499 | strcpy( modem->operators[1].name[0], OPERATOR_ROAMING_NAME ); |
| 500 | strcpy( modem->operators[1].name[1], OPERATOR_ROAMING_NAME ); |
| 501 | strcpy( modem->operators[1].name[2], OPERATOR_ROAMING_MCCMNC ); |
| 502 | |
| 503 | modem->operators[1].status = A_STATUS_AVAILABLE; |
| 504 | |
| 505 | modem->voice_mode = A_REGISTRATION_UNSOL_ENABLED_FULL; |
| 506 | modem->voice_state = A_REGISTRATION_HOME; |
| 507 | modem->data_mode = A_REGISTRATION_UNSOL_ENABLED_FULL; |
| 508 | modem->data_state = A_REGISTRATION_HOME; |
Jaime Lopez | 1a00085 | 2010-07-21 18:03:58 -0700 | [diff] [blame] | 509 | modem->data_network = A_DATA_NETWORK_UMTS; |
| 510 | |
| 511 | tmp = amodem_nvram_get_str( modem, NV_MODEM_TECHNOLOGY, "gsm" ); |
| 512 | modem->technology = android_parse_modem_tech( tmp ); |
| 513 | if (modem->technology == A_TECH_UNKNOWN) { |
| 514 | modem->technology = aconfig_int( modem->nvram_config, NV_MODEM_TECHNOLOGY, A_TECH_GSM ); |
| 515 | } |
| 516 | // Support GSM, WCDMA, CDMA, EvDo |
| 517 | modem->preferred_mask = amodem_nvram_get_int( modem, NV_PREFERRED_MODE, 0x0f ); |
| 518 | |
| 519 | modem->subscription_source = _amodem_get_cdma_subscription_source( modem ); |
| 520 | modem->roaming_pref = _amodem_get_cdma_roaming_preference( modem ); |
The Android Open Source Project | 8b23a6c | 2009-03-03 19:30:32 -0800 | [diff] [blame] | 521 | } |
| 522 | |
Tim Baverstock | e50d724 | 2010-12-21 19:20:04 +0000 | [diff] [blame] | 523 | static AVoiceCall amodem_alloc_call( AModem modem ); |
| 524 | static void amodem_free_call( AModem modem, AVoiceCall call ); |
| 525 | |
| 526 | #define MODEM_DEV_STATE_SAVE_VERSION 1 |
| 527 | |
| 528 | static void android_modem_state_save(QEMUFile *f, void *opaque) |
| 529 | { |
| 530 | AModem modem = opaque; |
| 531 | |
| 532 | // TODO: save more than just calls and call_count - rssi, power, etc. |
| 533 | |
| 534 | qemu_put_byte(f, modem->call_count); |
| 535 | |
| 536 | int nn; |
| 537 | for (nn = modem->call_count - 1; nn >= 0; nn--) { |
| 538 | AVoiceCall vcall = modem->calls + nn; |
| 539 | // Note: not saving timers or remote calls. |
| 540 | ACall call = &vcall->call; |
| 541 | qemu_put_byte( f, call->dir ); |
| 542 | qemu_put_byte( f, call->state ); |
| 543 | qemu_put_byte( f, call->mode ); |
| 544 | qemu_put_be32( f, call->multi ); |
| 545 | qemu_put_buffer( f, (uint8_t *)call->number, A_CALL_NUMBER_MAX_SIZE+1 ); |
| 546 | } |
| 547 | } |
| 548 | |
| 549 | static int android_modem_state_load(QEMUFile *f, void *opaque, int version_id) |
| 550 | { |
| 551 | if (version_id != MODEM_DEV_STATE_SAVE_VERSION) |
| 552 | return -1; |
| 553 | |
| 554 | AModem modem = opaque; |
| 555 | |
| 556 | // In case there are timers or remote calls. |
| 557 | int nn; |
| 558 | for (nn = modem->call_count - 1; nn >= 0; nn--) { |
| 559 | amodem_free_call( modem, modem->calls + nn); |
| 560 | } |
| 561 | |
| 562 | int call_count = qemu_get_byte(f); |
| 563 | for (nn = call_count; nn > 0; nn--) { |
| 564 | AVoiceCall vcall = amodem_alloc_call( modem ); |
| 565 | ACall call = &vcall->call; |
| 566 | call->dir = qemu_get_byte( f ); |
| 567 | call->state = qemu_get_byte( f ); |
| 568 | call->mode = qemu_get_byte( f ); |
| 569 | call->multi = qemu_get_be32( f ); |
| 570 | qemu_get_buffer( f, (uint8_t *)call->number, A_CALL_NUMBER_MAX_SIZE+1 ); |
| 571 | } |
| 572 | |
| 573 | return 0; // >=0 Happy |
| 574 | } |
| 575 | |
The Android Open Source Project | 8b23a6c | 2009-03-03 19:30:32 -0800 | [diff] [blame] | 576 | static AModemRec _android_modem[1]; |
| 577 | |
| 578 | AModem |
| 579 | amodem_create( int base_port, AModemUnsolFunc unsol_func, void* unsol_opaque ) |
| 580 | { |
| 581 | AModem modem = _android_modem; |
Jaime Lopez | 1a00085 | 2010-07-21 18:03:58 -0700 | [diff] [blame] | 582 | char nvfname[MAX_PATH]; |
| 583 | char *start = nvfname; |
| 584 | char *end = start + sizeof(nvfname); |
| 585 | |
| 586 | modem->base_port = base_port; |
| 587 | start = bufprint_config_file( start, end, "modem-nv-ram-" ); |
| 588 | start = bufprint( start, end, "%d", modem->base_port ); |
| 589 | modem->nvram_config_filename = strdup( nvfname ); |
The Android Open Source Project | 8b23a6c | 2009-03-03 19:30:32 -0800 | [diff] [blame] | 590 | |
| 591 | amodem_reset( modem ); |
David Turner | 6f290f2 | 2009-04-18 20:50:40 -0700 | [diff] [blame] | 592 | modem->supportsNetworkDataType = 1; |
The Android Open Source Project | 8b23a6c | 2009-03-03 19:30:32 -0800 | [diff] [blame] | 593 | modem->unsol_func = unsol_func; |
| 594 | modem->unsol_opaque = unsol_opaque; |
| 595 | |
Marc Petit-Huguenin | a1b379c | 2010-07-14 12:33:15 -0700 | [diff] [blame] | 596 | modem->sim = asimcard_create(base_port); |
The Android Open Source Project | 8b23a6c | 2009-03-03 19:30:32 -0800 | [diff] [blame] | 597 | |
David 'Digit' Turner | e92bc56 | 2010-09-07 06:21:25 -0700 | [diff] [blame] | 598 | sys_main_init(); |
David 'Digit' Turner | 5cb5c0b | 2014-02-17 16:04:03 +0100 | [diff] [blame] | 599 | register_savevm(NULL, |
| 600 | "android_modem", |
| 601 | 0, |
| 602 | MODEM_DEV_STATE_SAVE_VERSION, |
| 603 | android_modem_state_save, |
| 604 | android_modem_state_load, |
| 605 | modem); |
David 'Digit' Turner | e92bc56 | 2010-09-07 06:21:25 -0700 | [diff] [blame] | 606 | |
Jaime Lopez | 1a00085 | 2010-07-21 18:03:58 -0700 | [diff] [blame] | 607 | aconfig_save_file( modem->nvram_config, modem->nvram_config_filename ); |
The Android Open Source Project | 8b23a6c | 2009-03-03 19:30:32 -0800 | [diff] [blame] | 608 | return modem; |
| 609 | } |
| 610 | |
| 611 | void |
David Turner | 6f290f2 | 2009-04-18 20:50:40 -0700 | [diff] [blame] | 612 | amodem_set_legacy( AModem modem ) |
| 613 | { |
| 614 | modem->supportsNetworkDataType = 0; |
| 615 | } |
| 616 | |
| 617 | void |
The Android Open Source Project | 8b23a6c | 2009-03-03 19:30:32 -0800 | [diff] [blame] | 618 | amodem_destroy( AModem modem ) |
| 619 | { |
| 620 | asimcard_destroy( modem->sim ); |
| 621 | modem->sim = NULL; |
| 622 | } |
| 623 | |
| 624 | |
| 625 | static int |
| 626 | amodem_has_network( AModem modem ) |
| 627 | { |
| 628 | return !(modem->radio_state == A_RADIO_STATE_OFF || |
| 629 | modem->oper_index < 0 || |
| 630 | modem->oper_index >= modem->oper_count || |
| 631 | modem->oper_selection_mode == A_SELECTION_DEREGISTRATION ); |
| 632 | } |
| 633 | |
| 634 | |
| 635 | ARadioState |
| 636 | amodem_get_radio_state( AModem modem ) |
| 637 | { |
| 638 | return modem->radio_state; |
| 639 | } |
| 640 | |
| 641 | void |
| 642 | amodem_set_radio_state( AModem modem, ARadioState state ) |
| 643 | { |
| 644 | modem->radio_state = state; |
| 645 | } |
| 646 | |
| 647 | ASimCard |
| 648 | amodem_get_sim( AModem modem ) |
| 649 | { |
| 650 | return modem->sim; |
| 651 | } |
| 652 | |
| 653 | ARegistrationState |
| 654 | amodem_get_voice_registration( AModem modem ) |
| 655 | { |
| 656 | return modem->voice_state; |
| 657 | } |
| 658 | |
| 659 | void |
| 660 | amodem_set_voice_registration( AModem modem, ARegistrationState state ) |
| 661 | { |
| 662 | modem->voice_state = state; |
| 663 | |
| 664 | if (state == A_REGISTRATION_HOME) |
| 665 | modem->oper_index = OPERATOR_HOME_INDEX; |
| 666 | else if (state == A_REGISTRATION_ROAMING) |
| 667 | modem->oper_index = OPERATOR_ROAMING_INDEX; |
| 668 | |
| 669 | switch (modem->voice_mode) { |
| 670 | case A_REGISTRATION_UNSOL_ENABLED: |
| 671 | amodem_unsol( modem, "+CREG: %d,%d\r", |
| 672 | modem->voice_mode, modem->voice_state ); |
| 673 | break; |
| 674 | |
| 675 | case A_REGISTRATION_UNSOL_ENABLED_FULL: |
| 676 | amodem_unsol( modem, "+CREG: %d,%d, \"%04x\", \"%04x\"\r", |
| 677 | modem->voice_mode, modem->voice_state, |
David 'Digit' Turner | d7f019d | 2011-02-04 12:30:54 +0100 | [diff] [blame] | 678 | modem->area_code & 0xffff, modem->cell_id & 0xffff); |
The Android Open Source Project | 8b23a6c | 2009-03-03 19:30:32 -0800 | [diff] [blame] | 679 | break; |
| 680 | default: |
| 681 | ; |
| 682 | } |
| 683 | } |
| 684 | |
| 685 | ARegistrationState |
| 686 | amodem_get_data_registration( AModem modem ) |
| 687 | { |
| 688 | return modem->data_state; |
| 689 | } |
| 690 | |
| 691 | void |
| 692 | amodem_set_data_registration( AModem modem, ARegistrationState state ) |
| 693 | { |
| 694 | modem->data_state = state; |
| 695 | |
| 696 | switch (modem->data_mode) { |
| 697 | case A_REGISTRATION_UNSOL_ENABLED: |
| 698 | amodem_unsol( modem, "+CGREG: %d,%d\r", |
| 699 | modem->data_mode, modem->data_state ); |
| 700 | break; |
| 701 | |
| 702 | case A_REGISTRATION_UNSOL_ENABLED_FULL: |
David Turner | 6f290f2 | 2009-04-18 20:50:40 -0700 | [diff] [blame] | 703 | if (modem->supportsNetworkDataType) |
| 704 | amodem_unsol( modem, "+CGREG: %d,%d,\"%04x\",\"%04x\",\"%04x\"\r", |
| 705 | modem->data_mode, modem->data_state, |
David 'Digit' Turner | d7f019d | 2011-02-04 12:30:54 +0100 | [diff] [blame] | 706 | modem->area_code & 0xffff, modem->cell_id & 0xffff, |
David Turner | 6f290f2 | 2009-04-18 20:50:40 -0700 | [diff] [blame] | 707 | modem->data_network ); |
| 708 | else |
| 709 | amodem_unsol( modem, "+CGREG: %d,%d,\"%04x\",\"%04x\"\r", |
| 710 | modem->data_mode, modem->data_state, |
David 'Digit' Turner | d7f019d | 2011-02-04 12:30:54 +0100 | [diff] [blame] | 711 | modem->area_code & 0xffff, modem->cell_id & 0xffff ); |
The Android Open Source Project | 8b23a6c | 2009-03-03 19:30:32 -0800 | [diff] [blame] | 712 | break; |
| 713 | |
| 714 | default: |
| 715 | ; |
| 716 | } |
| 717 | } |
| 718 | |
Jaime Lopez | 1a00085 | 2010-07-21 18:03:58 -0700 | [diff] [blame] | 719 | static int |
| 720 | amodem_nvram_set( AModem modem, const char *name, const char *value ) |
The Android Open Source Project | 8b23a6c | 2009-03-03 19:30:32 -0800 | [diff] [blame] | 721 | { |
Jaime Lopez | 1a00085 | 2010-07-21 18:03:58 -0700 | [diff] [blame] | 722 | aconfig_set(modem->nvram_config, name, value); |
| 723 | return 0; |
| 724 | } |
| 725 | static AModemTech |
| 726 | tech_from_network_type( ADataNetworkType type ) |
| 727 | { |
| 728 | switch (type) { |
| 729 | case A_DATA_NETWORK_GPRS: |
| 730 | case A_DATA_NETWORK_EDGE: |
| 731 | case A_DATA_NETWORK_UMTS: |
| 732 | // TODO: Add A_TECH_WCDMA |
| 733 | return A_TECH_GSM; |
| 734 | case A_DATA_NETWORK_LTE: |
| 735 | return A_TECH_LTE; |
| 736 | case A_DATA_NETWORK_CDMA1X: |
| 737 | case A_DATA_NETWORK_EVDO: |
| 738 | return A_TECH_CDMA; |
| 739 | case A_DATA_NETWORK_UNKNOWN: |
| 740 | return A_TECH_UNKNOWN; |
| 741 | } |
| 742 | return A_TECH_UNKNOWN; |
| 743 | } |
| 744 | |
| 745 | void |
| 746 | amodem_set_data_network_type( AModem modem, ADataNetworkType type ) |
| 747 | { |
| 748 | AModemTech modemTech; |
The Android Open Source Project | 8b23a6c | 2009-03-03 19:30:32 -0800 | [diff] [blame] | 749 | modem->data_network = type; |
| 750 | amodem_set_data_registration( modem, modem->data_state ); |
Jaime Lopez | 1a00085 | 2010-07-21 18:03:58 -0700 | [diff] [blame] | 751 | modemTech = tech_from_network_type(type); |
| 752 | if (modem->unsol_func && modemTech != A_TECH_UNKNOWN) { |
| 753 | if (_amodem_switch_technology( modem, modemTech, modem->preferred_mask )) { |
| 754 | modem->unsol_func( modem->unsol_opaque, modem->out_buff ); |
| 755 | } |
| 756 | } |
The Android Open Source Project | 8b23a6c | 2009-03-03 19:30:32 -0800 | [diff] [blame] | 757 | } |
| 758 | |
| 759 | int |
| 760 | amodem_get_operator_name ( AModem modem, ANameIndex index, char* buffer, int buffer_size ) |
| 761 | { |
| 762 | AOperator oper; |
| 763 | int len; |
| 764 | |
| 765 | if ( (unsigned)modem->oper_index >= (unsigned)modem->oper_count || |
| 766 | (unsigned)index > 2 ) |
| 767 | return 0; |
| 768 | |
| 769 | oper = modem->operators + modem->oper_index; |
| 770 | len = strlen(oper->name[index]) + 1; |
| 771 | |
| 772 | if (buffer_size > len) |
| 773 | buffer_size = len; |
| 774 | |
| 775 | if (buffer_size > 0) { |
| 776 | memcpy( buffer, oper->name[index], buffer_size-1 ); |
| 777 | buffer[buffer_size] = 0; |
| 778 | } |
| 779 | return len; |
| 780 | } |
| 781 | |
| 782 | /* reset one operator name from a user-provided buffer, set buffer_size to -1 for zero-terminated strings */ |
| 783 | void |
| 784 | amodem_set_operator_name( AModem modem, ANameIndex index, const char* buffer, int buffer_size ) |
| 785 | { |
| 786 | AOperator oper; |
| 787 | int avail; |
| 788 | |
| 789 | if ( (unsigned)modem->oper_index >= (unsigned)modem->oper_count || |
| 790 | (unsigned)index > 2 ) |
| 791 | return; |
| 792 | |
| 793 | oper = modem->operators + modem->oper_index; |
| 794 | |
| 795 | avail = sizeof(oper->name[0]); |
| 796 | if (buffer_size < 0) |
| 797 | buffer_size = strlen(buffer); |
| 798 | if (buffer_size > avail-1) |
| 799 | buffer_size = avail-1; |
| 800 | memcpy( oper->name[index], buffer, buffer_size ); |
| 801 | oper->name[index][buffer_size] = 0; |
| 802 | } |
| 803 | |
| 804 | /** CALLS |
| 805 | **/ |
| 806 | int |
| 807 | amodem_get_call_count( AModem modem ) |
| 808 | { |
| 809 | return modem->call_count; |
| 810 | } |
| 811 | |
| 812 | ACall |
| 813 | amodem_get_call( AModem modem, int index ) |
| 814 | { |
| 815 | if ((unsigned)index >= (unsigned)modem->call_count) |
| 816 | return NULL; |
| 817 | |
| 818 | return &modem->calls[index].call; |
| 819 | } |
| 820 | |
| 821 | static AVoiceCall |
| 822 | amodem_alloc_call( AModem modem ) |
| 823 | { |
| 824 | AVoiceCall call = NULL; |
| 825 | int count = modem->call_count; |
| 826 | |
| 827 | if (count < MAX_CALLS) { |
| 828 | int id; |
| 829 | |
| 830 | /* find a valid id for this call */ |
| 831 | for (id = 0; id < modem->call_count; id++) { |
| 832 | int found = 0; |
| 833 | int nn; |
| 834 | for (nn = 0; nn < count; nn++) { |
| 835 | if ( modem->calls[nn].call.id == (id+1) ) { |
| 836 | found = 1; |
| 837 | break; |
| 838 | } |
| 839 | } |
| 840 | if (!found) |
| 841 | break; |
| 842 | } |
| 843 | call = modem->calls + count; |
| 844 | call->call.id = id + 1; |
| 845 | call->modem = modem; |
| 846 | |
| 847 | modem->call_count += 1; |
| 848 | } |
| 849 | return call; |
| 850 | } |
| 851 | |
| 852 | |
| 853 | static void |
| 854 | amodem_free_call( AModem modem, AVoiceCall call ) |
| 855 | { |
| 856 | int nn; |
| 857 | |
| 858 | if (call->timer) { |
| 859 | sys_timer_destroy( call->timer ); |
| 860 | call->timer = NULL; |
| 861 | } |
| 862 | |
| 863 | if (call->is_remote) { |
| 864 | remote_call_cancel( call->call.number, modem->base_port ); |
| 865 | call->is_remote = 0; |
| 866 | } |
| 867 | |
| 868 | for (nn = 0; nn < modem->call_count; nn++) { |
| 869 | if ( modem->calls + nn == call ) |
| 870 | break; |
| 871 | } |
| 872 | assert( nn < modem->call_count ); |
| 873 | |
| 874 | memmove( modem->calls + nn, |
| 875 | modem->calls + nn + 1, |
| 876 | (modem->call_count - 1 - nn)*sizeof(*call) ); |
| 877 | |
| 878 | modem->call_count -= 1; |
| 879 | } |
| 880 | |
| 881 | |
| 882 | static AVoiceCall |
| 883 | amodem_find_call( AModem modem, int id ) |
| 884 | { |
| 885 | int nn; |
| 886 | |
| 887 | for (nn = 0; nn < modem->call_count; nn++) { |
| 888 | AVoiceCall call = modem->calls + nn; |
| 889 | if (call->call.id == id) |
| 890 | return call; |
| 891 | } |
| 892 | return NULL; |
| 893 | } |
| 894 | |
| 895 | static void |
| 896 | amodem_send_calls_update( AModem modem ) |
| 897 | { |
| 898 | /* despite its name, this really tells the system that the call |
| 899 | * state has changed */ |
| 900 | amodem_unsol( modem, "RING\r" ); |
| 901 | } |
| 902 | |
| 903 | |
| 904 | int |
| 905 | amodem_add_inbound_call( AModem modem, const char* number ) |
| 906 | { |
| 907 | AVoiceCall vcall = amodem_alloc_call( modem ); |
| 908 | ACall call = &vcall->call; |
| 909 | int len; |
| 910 | |
| 911 | if (call == NULL) |
| 912 | return -1; |
| 913 | |
| 914 | call->dir = A_CALL_INBOUND; |
| 915 | call->state = A_CALL_INCOMING; |
| 916 | call->mode = A_CALL_VOICE; |
| 917 | call->multi = 0; |
| 918 | |
| 919 | vcall->is_remote = (remote_number_string_to_port(number) > 0); |
| 920 | |
| 921 | len = strlen(number); |
| 922 | if (len >= sizeof(call->number)) |
| 923 | len = sizeof(call->number)-1; |
| 924 | |
| 925 | memcpy( call->number, number, len ); |
| 926 | call->number[len] = 0; |
| 927 | |
| 928 | amodem_send_calls_update( modem ); |
| 929 | return 0; |
| 930 | } |
| 931 | |
| 932 | ACall |
| 933 | amodem_find_call_by_number( AModem modem, const char* number ) |
| 934 | { |
| 935 | AVoiceCall vcall = modem->calls; |
| 936 | AVoiceCall vend = vcall + modem->call_count; |
| 937 | |
| 938 | if (!number) |
| 939 | return NULL; |
| 940 | |
| 941 | for ( ; vcall < vend; vcall++ ) |
| 942 | if ( !strcmp(vcall->call.number, number) ) |
| 943 | return &vcall->call; |
| 944 | |
| 945 | return NULL; |
| 946 | } |
| 947 | |
Tim Baverstock | 4c6b10a | 2010-12-15 17:31:13 +0000 | [diff] [blame] | 948 | void |
| 949 | amodem_set_signal_strength( AModem modem, int rssi, int ber ) |
| 950 | { |
| 951 | modem->rssi = rssi; |
| 952 | modem->ber = ber; |
| 953 | } |
The Android Open Source Project | 8b23a6c | 2009-03-03 19:30:32 -0800 | [diff] [blame] | 954 | |
| 955 | static void |
| 956 | acall_set_state( AVoiceCall call, ACallState state ) |
| 957 | { |
| 958 | if (state != call->call.state) |
| 959 | { |
| 960 | if (call->is_remote) |
| 961 | { |
| 962 | const char* number = call->call.number; |
| 963 | int port = call->modem->base_port; |
| 964 | |
| 965 | switch (state) { |
| 966 | case A_CALL_HELD: |
| 967 | remote_call_other( number, port, REMOTE_CALL_HOLD ); |
| 968 | break; |
| 969 | |
| 970 | case A_CALL_ACTIVE: |
| 971 | remote_call_other( number, port, REMOTE_CALL_ACCEPT ); |
| 972 | break; |
| 973 | |
| 974 | default: ; |
| 975 | } |
| 976 | } |
| 977 | call->call.state = state; |
| 978 | } |
| 979 | } |
| 980 | |
| 981 | |
| 982 | int |
| 983 | amodem_update_call( AModem modem, const char* fromNumber, ACallState state ) |
| 984 | { |
| 985 | AVoiceCall vcall = (AVoiceCall) amodem_find_call_by_number(modem, fromNumber); |
| 986 | |
| 987 | if (vcall == NULL) |
| 988 | return -1; |
| 989 | |
| 990 | acall_set_state( vcall, state ); |
| 991 | amodem_send_calls_update(modem); |
| 992 | return 0; |
| 993 | } |
| 994 | |
| 995 | |
| 996 | int |
| 997 | amodem_disconnect_call( AModem modem, const char* number ) |
| 998 | { |
| 999 | AVoiceCall vcall = (AVoiceCall) amodem_find_call_by_number(modem, number); |
| 1000 | |
| 1001 | if (!vcall) |
| 1002 | return -1; |
| 1003 | |
| 1004 | amodem_free_call( modem, vcall ); |
| 1005 | amodem_send_calls_update(modem); |
| 1006 | return 0; |
| 1007 | } |
| 1008 | |
| 1009 | /** COMMAND HANDLERS |
| 1010 | **/ |
| 1011 | |
| 1012 | static const char* |
| 1013 | unknownCommand( const char* cmd, AModem modem ) |
| 1014 | { |
| 1015 | modem=modem; |
| 1016 | fprintf(stderr, ">>> unknown command '%s'\n", cmd ); |
| 1017 | return "ERROR: unknown command\r"; |
| 1018 | } |
| 1019 | |
Jaime Lopez | 1a00085 | 2010-07-21 18:03:58 -0700 | [diff] [blame] | 1020 | /* |
| 1021 | * Tell whether the specified tech is valid for the preferred mask. |
| 1022 | * @pmask: The preferred mask |
| 1023 | * @tech: The AModemTech we try to validate |
| 1024 | * return: If the specified technology is not set in any of the 4 |
| 1025 | * bitmasks, return 0. |
| 1026 | * Otherwise, return a non-zero value. |
| 1027 | */ |
| 1028 | static int matchPreferredMask( int32_t pmask, AModemTech tech ) |
| 1029 | { |
| 1030 | int ret = 0; |
| 1031 | int i; |
| 1032 | for ( i=3; i >= 0 ; i-- ) { |
| 1033 | if (pmask & (1 << (tech + i*8 ))) { |
| 1034 | ret = 1; |
| 1035 | break; |
| 1036 | } |
| 1037 | } |
| 1038 | return ret; |
| 1039 | } |
| 1040 | |
| 1041 | static AModemTech |
| 1042 | chooseTechFromMask( AModem modem, int32_t preferred ) |
| 1043 | { |
| 1044 | int i, j; |
| 1045 | |
| 1046 | /* TODO: Current implementation will only return the highest priority, |
| 1047 | * lowest numbered technology that is set in the mask. |
| 1048 | * However the implementation could be changed to consider currently |
| 1049 | * available networks set from the console (or somewhere else) |
| 1050 | */ |
| 1051 | for ( i=3 ; i >= 0; i-- ) { |
| 1052 | for ( j=0 ; j < A_TECH_UNKNOWN ; j++ ) { |
| 1053 | if (preferred & (1 << (j + 8 * i))) |
| 1054 | return (AModemTech) j; |
| 1055 | } |
| 1056 | } |
| 1057 | assert("This should never happen" == 0); |
| 1058 | // This should never happen. Just to please the compiler. |
| 1059 | return A_TECH_UNKNOWN; |
| 1060 | } |
| 1061 | |
| 1062 | static const char* |
| 1063 | _amodem_switch_technology( AModem modem, AModemTech newtech, int32_t newpreferred ) |
| 1064 | { |
| 1065 | D("_amodem_switch_technology: oldtech: %d, newtech %d, preferred: %d. newpreferred: %d\n", |
| 1066 | modem->technology, newtech, modem->preferred_mask,newpreferred); |
| 1067 | const char *ret = "+CTEC: DONE"; |
| 1068 | assert( modem ); |
| 1069 | |
| 1070 | if (!newpreferred) { |
| 1071 | return "ERROR: At least one technology must be enabled"; |
| 1072 | } |
| 1073 | if (modem->preferred_mask != newpreferred) { |
| 1074 | char value[MAX_KEY_NAME + 1]; |
| 1075 | modem->preferred_mask = newpreferred; |
| 1076 | snprintf(value, MAX_KEY_NAME, "%d", newpreferred); |
| 1077 | amodem_nvram_set(modem, NV_PREFERRED_MODE, value); |
| 1078 | if (!matchPreferredMask(modem->preferred_mask, newtech)) { |
| 1079 | newtech = chooseTechFromMask(modem, newpreferred); |
| 1080 | } |
| 1081 | } |
| 1082 | |
| 1083 | if (modem->technology != newtech) { |
| 1084 | modem->technology = newtech; |
| 1085 | ret = amodem_printf(modem, "+CTEC: %d", modem->technology); |
| 1086 | } |
| 1087 | return ret; |
| 1088 | } |
| 1089 | |
| 1090 | static int |
| 1091 | parsePreferred( const char *str, int *preferred ) |
| 1092 | { |
| 1093 | char *endptr = NULL; |
| 1094 | int result = 0; |
| 1095 | if (!str || !*str) { *preferred = 0; return 0; } |
| 1096 | if (*str == '"') str ++; |
| 1097 | if (!*str) return 0; |
| 1098 | |
| 1099 | result = strtol(str, &endptr, 16); |
| 1100 | if (*endptr && *endptr != '"') { |
| 1101 | return 0; |
| 1102 | } |
| 1103 | if (preferred) |
| 1104 | *preferred = result; |
| 1105 | return 1; |
| 1106 | } |
| 1107 | |
| 1108 | void |
| 1109 | amodem_set_cdma_prl_version( AModem modem, int prlVersion) |
| 1110 | { |
| 1111 | D("amodem_set_prl_version()\n"); |
| 1112 | if (!_amodem_set_cdma_prl_version( modem, prlVersion)) { |
| 1113 | amodem_unsol(modem, "+WPRL: %d", prlVersion); |
| 1114 | } |
| 1115 | } |
| 1116 | |
| 1117 | static int |
| 1118 | _amodem_set_cdma_prl_version( AModem modem, int prlVersion) |
| 1119 | { |
| 1120 | D("_amodem_set_cdma_prl_version"); |
| 1121 | if (modem->prl_version != prlVersion) { |
| 1122 | modem->prl_version = prlVersion; |
| 1123 | return 0; |
| 1124 | } |
| 1125 | return -1; |
| 1126 | } |
| 1127 | |
| 1128 | void |
| 1129 | amodem_set_cdma_subscription_source( AModem modem, ACdmaSubscriptionSource ss) |
| 1130 | { |
| 1131 | D("amodem_set_cdma_subscription_source()\n"); |
| 1132 | if (!_amodem_set_cdma_subscription_source( modem, ss)) { |
| 1133 | amodem_unsol(modem, "+CCSS: %d", (int)ss); |
| 1134 | } |
| 1135 | } |
| 1136 | |
| 1137 | #define MAX_INT_DIGITS 10 |
| 1138 | static int |
| 1139 | _amodem_set_cdma_subscription_source( AModem modem, ACdmaSubscriptionSource ss) |
| 1140 | { |
| 1141 | D("_amodem_set_cdma_subscription_source()\n"); |
| 1142 | char value[MAX_INT_DIGITS + 1]; |
| 1143 | |
| 1144 | if (ss != modem->subscription_source) { |
| 1145 | snprintf( value, MAX_INT_DIGITS + 1, "%d", ss ); |
| 1146 | amodem_nvram_set( modem, NV_CDMA_SUBSCRIPTION_SOURCE, value ); |
| 1147 | modem->subscription_source = ss; |
| 1148 | return 0; |
| 1149 | } |
| 1150 | return -1; |
| 1151 | } |
| 1152 | |
| 1153 | static const char* |
| 1154 | handleSubscriptionSource( const char* cmd, AModem modem ) |
| 1155 | { |
| 1156 | int newsource; |
| 1157 | // TODO: Actually change subscription depending on source |
| 1158 | D("handleSubscriptionSource(%s)\n",cmd); |
| 1159 | |
| 1160 | assert( !memcmp( "+CCSS", cmd, 5 ) ); |
| 1161 | cmd += 5; |
| 1162 | if (cmd[0] == '?') { |
| 1163 | return amodem_printf( modem, "+CCSS: %d", modem->subscription_source ); |
| 1164 | } else if (cmd[0] == '=') { |
| 1165 | switch (cmd[1]) { |
| 1166 | case '0': |
| 1167 | case '1': |
| 1168 | newsource = (ACdmaSubscriptionSource)cmd[1] - '0'; |
| 1169 | _amodem_set_cdma_subscription_source( modem, newsource ); |
| 1170 | return amodem_printf( modem, "+CCSS: %d", modem->subscription_source ); |
| 1171 | break; |
| 1172 | } |
| 1173 | } |
| 1174 | return amodem_printf( modem, "ERROR: Invalid subscription source"); |
| 1175 | } |
| 1176 | |
| 1177 | static const char* |
| 1178 | handleRoamPref( const char * cmd, AModem modem ) |
| 1179 | { |
| 1180 | int roaming_pref = -1; |
| 1181 | char *endptr = NULL; |
| 1182 | D("handleRoamPref(%s)\n", cmd); |
| 1183 | assert( !memcmp( "+WRMP", cmd, 5 ) ); |
| 1184 | cmd += 5; |
| 1185 | if (cmd[0] == '?') { |
| 1186 | return amodem_printf( modem, "+WRMP: %d", modem->roaming_pref ); |
| 1187 | } |
| 1188 | |
| 1189 | if (!strcmp( cmd, "=?")) { |
| 1190 | return amodem_printf( modem, "+WRMP: 0,1,2" ); |
| 1191 | } else if (cmd[0] == '=') { |
| 1192 | cmd ++; |
| 1193 | roaming_pref = strtol( cmd, &endptr, 10 ); |
| 1194 | // Make sure the rest of the command is the number |
| 1195 | // (if *endptr is null, it means strtol processed the whole string as a number) |
| 1196 | if(endptr && !*endptr) { |
| 1197 | modem->roaming_pref = roaming_pref; |
| 1198 | aconfig_set( modem->nvram_config, NV_CDMA_ROAMING_PREF, cmd ); |
| 1199 | aconfig_save_file( modem->nvram_config, modem->nvram_config_filename ); |
| 1200 | return NULL; |
| 1201 | } |
| 1202 | } |
| 1203 | return amodem_printf( modem, "ERROR"); |
| 1204 | } |
| 1205 | static const char* |
| 1206 | handleTech( const char* cmd, AModem modem ) |
| 1207 | { |
| 1208 | AModemTech newtech = modem->technology; |
| 1209 | int pt = modem->preferred_mask; |
| 1210 | int havenewtech = 0; |
| 1211 | D("handleTech. cmd: %s\n", cmd); |
| 1212 | assert( !memcmp( "+CTEC", cmd, 5 ) ); |
| 1213 | cmd += 5; |
| 1214 | if (cmd[0] == '?') { |
| 1215 | return amodem_printf( modem, "+CTEC: %d,%x",modem->technology, modem->preferred_mask ); |
| 1216 | } |
| 1217 | amodem_begin_line( modem ); |
| 1218 | if (!strcmp( cmd, "=?")) { |
| 1219 | return amodem_printf( modem, "+CTEC: 0,1,2,3" ); |
| 1220 | } |
| 1221 | else if (cmd[0] == '=') { |
| 1222 | switch (cmd[1]) { |
| 1223 | case '0': |
| 1224 | case '1': |
| 1225 | case '2': |
| 1226 | case '3': |
| 1227 | havenewtech = 1; |
| 1228 | newtech = cmd[1] - '0'; |
| 1229 | cmd += 1; |
| 1230 | break; |
| 1231 | } |
| 1232 | cmd += 1; |
| 1233 | } |
| 1234 | if (havenewtech) { |
| 1235 | D( "cmd: %s\n", cmd ); |
| 1236 | if (cmd[0] == ',' && ! parsePreferred( ++cmd, &pt )) |
| 1237 | return amodem_printf( modem, "ERROR: invalid preferred mode" ); |
| 1238 | return _amodem_switch_technology( modem, newtech, pt ); |
| 1239 | } |
| 1240 | return amodem_printf( modem, "ERROR: %s: Unknown Technology", cmd + 1 ); |
| 1241 | } |
| 1242 | |
| 1243 | static const char* |
| 1244 | handleEmergencyMode( const char* cmd, AModem modem ) |
| 1245 | { |
| 1246 | long arg; |
| 1247 | char *endptr = NULL; |
| 1248 | assert ( !memcmp( "+WSOS", cmd, 5 ) ); |
| 1249 | cmd += 5; |
| 1250 | if (cmd[0] == '?') { |
| 1251 | return amodem_printf( modem, "+WSOS: %d", modem->in_emergency_mode); |
| 1252 | } |
| 1253 | |
| 1254 | if (cmd[0] == '=') { |
| 1255 | if (cmd[1] == '?') { |
| 1256 | return amodem_printf(modem, "+WSOS: (0)"); |
| 1257 | } |
| 1258 | if (cmd[1] == 0) { |
| 1259 | return amodem_printf(modem, "ERROR"); |
| 1260 | } |
| 1261 | arg = strtol(cmd+1, &endptr, 10); |
| 1262 | |
| 1263 | if (!endptr || endptr[0] != 0) { |
| 1264 | return amodem_printf(modem, "ERROR"); |
| 1265 | } |
| 1266 | |
| 1267 | arg = arg? 1 : 0; |
| 1268 | |
| 1269 | if ((!arg) != (!modem->in_emergency_mode)) { |
| 1270 | modem->in_emergency_mode = arg; |
| 1271 | return amodem_printf(modem, "+WSOS: %d", arg); |
| 1272 | } |
| 1273 | } |
| 1274 | return amodem_printf(modem, "ERROR"); |
| 1275 | } |
| 1276 | |
| 1277 | static const char* |
| 1278 | handlePrlVersion( const char* cmd, AModem modem ) |
| 1279 | { |
Jaime Lopez | 1a00085 | 2010-07-21 18:03:58 -0700 | [diff] [blame] | 1280 | assert ( !memcmp( "+WPRL", cmd, 5 ) ); |
| 1281 | cmd += 5; |
| 1282 | if (cmd[0] == '?') { |
| 1283 | return amodem_printf( modem, "+WPRL: %d", modem->prl_version); |
| 1284 | } |
| 1285 | |
| 1286 | return amodem_printf(modem, "ERROR"); |
| 1287 | } |
| 1288 | |
The Android Open Source Project | 8b23a6c | 2009-03-03 19:30:32 -0800 | [diff] [blame] | 1289 | static const char* |
| 1290 | handleRadioPower( const char* cmd, AModem modem ) |
| 1291 | { |
| 1292 | if ( !strcmp( cmd, "+CFUN=0" ) ) |
| 1293 | { |
| 1294 | /* turn radio off */ |
| 1295 | modem->radio_state = A_RADIO_STATE_OFF; |
| 1296 | } |
| 1297 | else if ( !strcmp( cmd, "+CFUN=1" ) ) |
| 1298 | { |
| 1299 | /* turn radio on */ |
| 1300 | modem->radio_state = A_RADIO_STATE_ON; |
| 1301 | } |
| 1302 | return NULL; |
| 1303 | } |
| 1304 | |
| 1305 | static const char* |
| 1306 | handleRadioPowerReq( const char* cmd, AModem modem ) |
| 1307 | { |
| 1308 | if (modem->radio_state != A_RADIO_STATE_OFF) |
Jaime Lopez | 1a00085 | 2010-07-21 18:03:58 -0700 | [diff] [blame] | 1309 | return "+CFUN: 1"; |
The Android Open Source Project | 8b23a6c | 2009-03-03 19:30:32 -0800 | [diff] [blame] | 1310 | else |
Jaime Lopez | 1a00085 | 2010-07-21 18:03:58 -0700 | [diff] [blame] | 1311 | return "+CFUN: 0"; |
The Android Open Source Project | 8b23a6c | 2009-03-03 19:30:32 -0800 | [diff] [blame] | 1312 | } |
| 1313 | |
| 1314 | static const char* |
| 1315 | handleSIMStatusReq( const char* cmd, AModem modem ) |
| 1316 | { |
| 1317 | const char* answer = NULL; |
| 1318 | |
| 1319 | switch (asimcard_get_status(modem->sim)) { |
| 1320 | case A_SIM_STATUS_ABSENT: answer = "+CPIN: ABSENT"; break; |
| 1321 | case A_SIM_STATUS_READY: answer = "+CPIN: READY"; break; |
| 1322 | case A_SIM_STATUS_NOT_READY: answer = "+CMERROR: NOT READY"; break; |
| 1323 | case A_SIM_STATUS_PIN: answer = "+CPIN: SIM PIN"; break; |
| 1324 | case A_SIM_STATUS_PUK: answer = "+CPIN: SIM PUK"; break; |
| 1325 | case A_SIM_STATUS_NETWORK_PERSONALIZATION: answer = "+CPIN: PH-NET PIN"; break; |
| 1326 | default: |
| 1327 | answer = "ERROR: internal error"; |
| 1328 | } |
| 1329 | return answer; |
| 1330 | } |
| 1331 | |
Jaime Lopez | 1a00085 | 2010-07-21 18:03:58 -0700 | [diff] [blame] | 1332 | /* TODO: Will we need this? |
| 1333 | static const char* |
| 1334 | handleSRegister( const char * cmd, AModem modem ) |
| 1335 | { |
| 1336 | char *end; |
| 1337 | assert( cmd[0] == 'S' || cmd[0] == 's' ); |
| 1338 | |
| 1339 | ++ cmd; |
| 1340 | |
| 1341 | int l = strtol(cmd, &end, 10); |
| 1342 | } */ |
| 1343 | |
The Android Open Source Project | 8b23a6c | 2009-03-03 19:30:32 -0800 | [diff] [blame] | 1344 | static const char* |
| 1345 | handleNetworkRegistration( const char* cmd, AModem modem ) |
| 1346 | { |
| 1347 | if ( !memcmp( cmd, "+CREG", 5 ) ) { |
| 1348 | cmd += 5; |
| 1349 | if (cmd[0] == '?') { |
David 'Digit' Turner | d7f019d | 2011-02-04 12:30:54 +0100 | [diff] [blame] | 1350 | if (modem->voice_mode == A_REGISTRATION_UNSOL_ENABLED_FULL) |
| 1351 | return amodem_printf( modem, "+CREG: %d,%d, \"%04x\", \"%04x\"", |
| 1352 | modem->voice_mode, modem->voice_state, |
| 1353 | modem->area_code, modem->cell_id ); |
| 1354 | else |
| 1355 | return amodem_printf( modem, "+CREG: %d,%d", |
| 1356 | modem->voice_mode, modem->voice_state ); |
The Android Open Source Project | 8b23a6c | 2009-03-03 19:30:32 -0800 | [diff] [blame] | 1357 | } else if (cmd[0] == '=') { |
| 1358 | switch (cmd[1]) { |
| 1359 | case '0': |
| 1360 | modem->voice_mode = A_REGISTRATION_UNSOL_DISABLED; |
| 1361 | break; |
| 1362 | |
| 1363 | case '1': |
| 1364 | modem->voice_mode = A_REGISTRATION_UNSOL_ENABLED; |
| 1365 | break; |
| 1366 | |
| 1367 | case '2': |
| 1368 | modem->voice_mode = A_REGISTRATION_UNSOL_ENABLED_FULL; |
| 1369 | break; |
| 1370 | |
| 1371 | case '?': |
| 1372 | return "+CREG: (0-2)"; |
| 1373 | |
| 1374 | default: |
| 1375 | return "ERROR: BAD COMMAND"; |
| 1376 | } |
| 1377 | } else { |
| 1378 | assert( 0 && "unreachable" ); |
| 1379 | } |
| 1380 | } else if ( !memcmp( cmd, "+CGREG", 6 ) ) { |
| 1381 | cmd += 6; |
David Turner | 6f290f2 | 2009-04-18 20:50:40 -0700 | [diff] [blame] | 1382 | if (cmd[0] == '?') { |
| 1383 | if (modem->supportsNetworkDataType) |
| 1384 | return amodem_printf( modem, "+CGREG: %d,%d,\"%04x\",\"%04x\",\"%04x\"", |
| 1385 | modem->data_mode, modem->data_state, |
| 1386 | modem->area_code, modem->cell_id, |
| 1387 | modem->data_network ); |
| 1388 | else |
| 1389 | return amodem_printf( modem, "+CGREG: %d,%d,\"%04x\",\"%04x\"", |
| 1390 | modem->data_mode, modem->data_state, |
| 1391 | modem->area_code, modem->cell_id ); |
The Android Open Source Project | 8b23a6c | 2009-03-03 19:30:32 -0800 | [diff] [blame] | 1392 | } else if (cmd[0] == '=') { |
| 1393 | switch (cmd[1]) { |
| 1394 | case '0': |
| 1395 | modem->data_mode = A_REGISTRATION_UNSOL_DISABLED; |
| 1396 | break; |
| 1397 | |
| 1398 | case '1': |
| 1399 | modem->data_mode = A_REGISTRATION_UNSOL_ENABLED; |
| 1400 | break; |
| 1401 | |
| 1402 | case '2': |
| 1403 | modem->data_mode = A_REGISTRATION_UNSOL_ENABLED_FULL; |
| 1404 | break; |
| 1405 | |
| 1406 | case '?': |
| 1407 | return "+CGREG: (0-2)"; |
| 1408 | |
| 1409 | default: |
| 1410 | return "ERROR: BAD COMMAND"; |
| 1411 | } |
| 1412 | } else { |
| 1413 | assert( 0 && "unreachable" ); |
| 1414 | } |
| 1415 | } |
| 1416 | return NULL; |
| 1417 | } |
| 1418 | |
| 1419 | static const char* |
| 1420 | handleSetDialTone( const char* cmd, AModem modem ) |
| 1421 | { |
| 1422 | /* XXX: TODO */ |
| 1423 | return NULL; |
| 1424 | } |
| 1425 | |
| 1426 | static const char* |
| 1427 | handleDeleteSMSonSIM( const char* cmd, AModem modem ) |
| 1428 | { |
| 1429 | /* XXX: TODO */ |
| 1430 | return NULL; |
| 1431 | } |
| 1432 | |
| 1433 | static const char* |
| 1434 | handleSIM_IO( const char* cmd, AModem modem ) |
| 1435 | { |
| 1436 | return asimcard_io( modem->sim, cmd ); |
| 1437 | } |
| 1438 | |
| 1439 | |
| 1440 | static const char* |
| 1441 | handleOperatorSelection( const char* cmd, AModem modem ) |
| 1442 | { |
| 1443 | assert( !memcmp( "+COPS", cmd, 5 ) ); |
| 1444 | cmd += 5; |
| 1445 | if (cmd[0] == '?') { /* ask for current operator */ |
| 1446 | AOperator oper = &modem->operators[ modem->oper_index ]; |
| 1447 | |
| 1448 | if ( !amodem_has_network( modem ) ) |
| 1449 | { |
| 1450 | /* this error code means "no network" */ |
| 1451 | return amodem_printf( modem, "+CME ERROR: 30" ); |
| 1452 | } |
| 1453 | |
| 1454 | oper = &modem->operators[ modem->oper_index ]; |
| 1455 | |
| 1456 | if ( modem->oper_name_index == 2 ) |
| 1457 | return amodem_printf( modem, "+COPS: %d,2,%s", |
| 1458 | modem->oper_selection_mode, |
| 1459 | oper->name[2] ); |
| 1460 | |
| 1461 | return amodem_printf( modem, "+COPS: %d,%d,\"%s\"", |
| 1462 | modem->oper_selection_mode, |
| 1463 | modem->oper_name_index, |
| 1464 | oper->name[ modem->oper_name_index ] ); |
| 1465 | } |
| 1466 | else if (cmd[0] == '=' && cmd[1] == '?') { /* ask for all available operators */ |
| 1467 | const char* comma = "+COPS: "; |
| 1468 | int nn; |
| 1469 | amodem_begin_line( modem ); |
| 1470 | for (nn = 0; nn < modem->oper_count; nn++) { |
| 1471 | AOperator oper = &modem->operators[nn]; |
| 1472 | amodem_add_line( modem, "%s(%d,\"%s\",\"%s\",\"%s\")", comma, |
| 1473 | oper->status, oper->name[0], oper->name[1], oper->name[2] ); |
| 1474 | comma = ", "; |
| 1475 | } |
| 1476 | return amodem_end_line( modem ); |
| 1477 | } |
| 1478 | else if (cmd[0] == '=') { |
| 1479 | switch (cmd[1]) { |
| 1480 | case '0': |
| 1481 | modem->oper_selection_mode = A_SELECTION_AUTOMATIC; |
| 1482 | return NULL; |
| 1483 | |
| 1484 | case '1': |
| 1485 | { |
| 1486 | int format, nn, len, found = -1; |
| 1487 | |
| 1488 | if (cmd[2] != ',') |
| 1489 | goto BadCommand; |
| 1490 | format = cmd[3] - '0'; |
| 1491 | if ( (unsigned)format > 2 ) |
| 1492 | goto BadCommand; |
| 1493 | if (cmd[4] != ',') |
| 1494 | goto BadCommand; |
| 1495 | cmd += 5; |
| 1496 | len = strlen(cmd); |
| 1497 | if (*cmd == '"') { |
| 1498 | cmd++; |
| 1499 | len -= 2; |
| 1500 | } |
| 1501 | if (len <= 0) |
| 1502 | goto BadCommand; |
| 1503 | |
| 1504 | for (nn = 0; nn < modem->oper_count; nn++) { |
| 1505 | AOperator oper = modem->operators + nn; |
| 1506 | char* name = oper->name[ format ]; |
| 1507 | |
| 1508 | if ( !memcpy( name, cmd, len ) && name[len] == 0 ) { |
| 1509 | found = nn; |
| 1510 | break; |
| 1511 | } |
| 1512 | } |
| 1513 | |
| 1514 | if (found < 0) { |
| 1515 | /* Selection failed */ |
| 1516 | return "+CME ERROR: 529"; |
| 1517 | } else if (modem->operators[found].status == A_STATUS_DENIED) { |
| 1518 | /* network not allowed */ |
| 1519 | return "+CME ERROR: 32"; |
| 1520 | } |
| 1521 | modem->oper_index = found; |
| 1522 | |
| 1523 | /* set the voice and data registration states to home or roaming |
| 1524 | * depending on the operator index |
| 1525 | */ |
| 1526 | if (found == OPERATOR_HOME_INDEX) { |
| 1527 | modem->voice_state = A_REGISTRATION_HOME; |
| 1528 | modem->data_state = A_REGISTRATION_HOME; |
| 1529 | } else if (found == OPERATOR_ROAMING_INDEX) { |
| 1530 | modem->voice_state = A_REGISTRATION_ROAMING; |
| 1531 | modem->data_state = A_REGISTRATION_ROAMING; |
| 1532 | } |
| 1533 | return NULL; |
| 1534 | } |
| 1535 | |
| 1536 | case '2': |
| 1537 | modem->oper_selection_mode = A_SELECTION_DEREGISTRATION; |
| 1538 | return NULL; |
| 1539 | |
| 1540 | case '3': |
| 1541 | { |
| 1542 | int format; |
| 1543 | |
| 1544 | if (cmd[2] != ',') |
| 1545 | goto BadCommand; |
| 1546 | |
| 1547 | format = cmd[3] - '0'; |
| 1548 | if ( (unsigned)format > 2 ) |
| 1549 | goto BadCommand; |
| 1550 | |
| 1551 | modem->oper_name_index = format; |
| 1552 | return NULL; |
| 1553 | } |
| 1554 | default: |
| 1555 | ; |
| 1556 | } |
| 1557 | } |
| 1558 | BadCommand: |
| 1559 | return unknownCommand(cmd,modem); |
| 1560 | } |
| 1561 | |
| 1562 | static const char* |
| 1563 | handleRequestOperator( const char* cmd, AModem modem ) |
| 1564 | { |
| 1565 | AOperator oper; |
| 1566 | cmd=cmd; |
| 1567 | |
| 1568 | if ( !amodem_has_network(modem) ) |
| 1569 | return "+CME ERROR: 30"; |
| 1570 | |
| 1571 | oper = modem->operators + modem->oper_index; |
| 1572 | modem->oper_name_index = 2; |
| 1573 | return amodem_printf( modem, "+COPS: 0,0,\"%s\"\r" |
| 1574 | "+COPS: 0,1,\"%s\"\r" |
| 1575 | "+COPS: 0,2,\"%s\"", |
| 1576 | oper->name[0], oper->name[1], oper->name[2] ); |
| 1577 | } |
| 1578 | |
| 1579 | static const char* |
| 1580 | handleSendSMStoSIM( const char* cmd, AModem modem ) |
| 1581 | { |
| 1582 | /* XXX: TODO */ |
| 1583 | return "ERROR: unimplemented"; |
| 1584 | } |
| 1585 | |
| 1586 | static const char* |
| 1587 | handleSendSMS( const char* cmd, AModem modem ) |
| 1588 | { |
| 1589 | modem->wait_sms = 1; |
| 1590 | return "> "; |
| 1591 | } |
| 1592 | |
| 1593 | #if 0 |
| 1594 | static void |
| 1595 | sms_address_dump( SmsAddress address, FILE* out ) |
| 1596 | { |
| 1597 | int nn, len = address->len; |
| 1598 | |
| 1599 | if (address->toa == 0x91) { |
| 1600 | fprintf( out, "+" ); |
| 1601 | } |
| 1602 | for (nn = 0; nn < len; nn += 2) |
| 1603 | { |
| 1604 | static const char dialdigits[16] = "0123456789*#,N%"; |
| 1605 | int c = address->data[nn/2]; |
| 1606 | |
| 1607 | fprintf( out, "%c", dialdigits[c & 0xf] ); |
| 1608 | if (nn+1 >= len) |
| 1609 | break; |
| 1610 | |
| 1611 | fprintf( out, "%c", dialdigits[(c >> 4) & 0xf] ); |
| 1612 | } |
| 1613 | } |
| 1614 | |
| 1615 | static void |
| 1616 | smspdu_dump( SmsPDU pdu, FILE* out ) |
| 1617 | { |
| 1618 | SmsAddressRec address; |
| 1619 | unsigned char temp[256]; |
| 1620 | int len; |
| 1621 | |
| 1622 | if (pdu == NULL) { |
| 1623 | fprintf( out, "SMS PDU is (null)\n" ); |
| 1624 | return; |
| 1625 | } |
| 1626 | |
| 1627 | fprintf( out, "SMS PDU type: " ); |
| 1628 | switch (smspdu_get_type(pdu)) { |
| 1629 | case SMS_PDU_DELIVER: fprintf(out, "DELIVER"); break; |
| 1630 | case SMS_PDU_SUBMIT: fprintf(out, "SUBMIT"); break; |
| 1631 | case SMS_PDU_STATUS_REPORT: fprintf(out, "STATUS_REPORT"); break; |
| 1632 | default: fprintf(out, "UNKNOWN"); |
| 1633 | } |
| 1634 | fprintf( out, "\n sender: " ); |
| 1635 | if (smspdu_get_sender_address(pdu, &address) < 0) |
| 1636 | fprintf( out, "(N/A)" ); |
| 1637 | else |
| 1638 | sms_address_dump(&address, out); |
| 1639 | fprintf( out, "\n receiver: " ); |
| 1640 | if (smspdu_get_receiver_address(pdu, &address) < 0) |
| 1641 | fprintf(out, "(N/A)"); |
| 1642 | else |
| 1643 | sms_address_dump(&address, out); |
| 1644 | fprintf( out, "\n text: " ); |
| 1645 | len = smspdu_get_text_message( pdu, temp, sizeof(temp)-1 ); |
| 1646 | if (len > sizeof(temp)-1 ) |
| 1647 | len = sizeof(temp)-1; |
| 1648 | fprintf( out, "'%.*s'\n", len, temp ); |
| 1649 | } |
| 1650 | #endif |
| 1651 | |
| 1652 | static const char* |
| 1653 | handleSendSMSText( const char* cmd, AModem modem ) |
| 1654 | { |
| 1655 | #if 1 |
| 1656 | SmsAddressRec address; |
Marc Petit-Huguenin | a1b379c | 2010-07-14 12:33:15 -0700 | [diff] [blame] | 1657 | char temp[16]; |
The Android Open Source Project | 8b23a6c | 2009-03-03 19:30:32 -0800 | [diff] [blame] | 1658 | char number[16]; |
| 1659 | int numlen; |
| 1660 | int len = strlen(cmd); |
| 1661 | SmsPDU pdu; |
| 1662 | |
| 1663 | /* get rid of trailing escape */ |
| 1664 | if (len > 0 && cmd[len-1] == 0x1a) |
| 1665 | len -= 1; |
| 1666 | |
| 1667 | pdu = smspdu_create_from_hex( cmd, len ); |
| 1668 | if (pdu == NULL) { |
| 1669 | D("%s: invalid SMS PDU ?: '%s'\n", __FUNCTION__, cmd); |
| 1670 | return "+CMS ERROR: INVALID SMS PDU"; |
| 1671 | } |
| 1672 | if (smspdu_get_receiver_address(pdu, &address) < 0) { |
| 1673 | D("%s: could not get SMS receiver address from '%s'\n", |
| 1674 | __FUNCTION__, cmd); |
| 1675 | return "+CMS ERROR: BAD SMS RECEIVER ADDRESS"; |
| 1676 | } |
| 1677 | |
| 1678 | do { |
| 1679 | int index; |
| 1680 | |
Marc Petit-Huguenin | a1b379c | 2010-07-14 12:33:15 -0700 | [diff] [blame] | 1681 | numlen = sms_address_to_str( &address, temp, sizeof(temp) ); |
| 1682 | if (numlen > sizeof(temp)-1) |
The Android Open Source Project | 8b23a6c | 2009-03-03 19:30:32 -0800 | [diff] [blame] | 1683 | break; |
Marc Petit-Huguenin | a1b379c | 2010-07-14 12:33:15 -0700 | [diff] [blame] | 1684 | temp[numlen] = 0; |
The Android Open Source Project | 8b23a6c | 2009-03-03 19:30:32 -0800 | [diff] [blame] | 1685 | |
Marc Petit-Huguenin | a1b379c | 2010-07-14 12:33:15 -0700 | [diff] [blame] | 1686 | /* Converts 4, 7, and 10 digits number to 11 digits */ |
| 1687 | if (numlen == 10 && !strncmp(temp, PHONE_PREFIX+1, 6)) { |
| 1688 | memcpy( number, PHONE_PREFIX, 1 ); |
| 1689 | memcpy( number+1, temp, numlen ); |
| 1690 | number[numlen+1] = 0; |
| 1691 | } else if (numlen == 7 && !strncmp(temp, PHONE_PREFIX+4, 3)) { |
| 1692 | memcpy( number, PHONE_PREFIX, 4 ); |
| 1693 | memcpy( number+4, temp, numlen ); |
| 1694 | number[numlen+4] = 0; |
| 1695 | } else if (numlen == 4) { |
| 1696 | memcpy( number, PHONE_PREFIX, 7 ); |
| 1697 | memcpy( number+7, temp, numlen ); |
| 1698 | number[numlen+7] = 0; |
| 1699 | } else { |
| 1700 | memcpy( number, temp, numlen ); |
| 1701 | number[numlen] = 0; |
| 1702 | } |
| 1703 | |
The Android Open Source Project | 8b23a6c | 2009-03-03 19:30:32 -0800 | [diff] [blame] | 1704 | if ( remote_number_string_to_port( number ) < 0 ) |
| 1705 | break; |
| 1706 | |
| 1707 | if (modem->sms_receiver == NULL) { |
| 1708 | modem->sms_receiver = sms_receiver_create(); |
| 1709 | if (modem->sms_receiver == NULL) { |
| 1710 | D( "%s: could not create SMS receiver\n", __FUNCTION__ ); |
| 1711 | break; |
| 1712 | } |
| 1713 | } |
| 1714 | |
| 1715 | index = sms_receiver_add_submit_pdu( modem->sms_receiver, pdu ); |
| 1716 | if (index < 0) { |
| 1717 | D( "%s: could not add submit PDU\n", __FUNCTION__ ); |
| 1718 | break; |
| 1719 | } |
| 1720 | /* the PDU is now owned by the receiver */ |
| 1721 | pdu = NULL; |
| 1722 | |
| 1723 | if (index > 0) { |
| 1724 | SmsAddressRec from[1]; |
Marc Petit-Huguenin | a1b379c | 2010-07-14 12:33:15 -0700 | [diff] [blame] | 1725 | char temp[12]; |
The Android Open Source Project | 8b23a6c | 2009-03-03 19:30:32 -0800 | [diff] [blame] | 1726 | SmsPDU* deliver; |
| 1727 | int nn; |
| 1728 | |
Marc Petit-Huguenin | a1b379c | 2010-07-14 12:33:15 -0700 | [diff] [blame] | 1729 | snprintf( temp, sizeof(temp), PHONE_PREFIX "%d", modem->base_port ); |
The Android Open Source Project | 8b23a6c | 2009-03-03 19:30:32 -0800 | [diff] [blame] | 1730 | sms_address_from_str( from, temp, strlen(temp) ); |
| 1731 | |
| 1732 | deliver = sms_receiver_create_deliver( modem->sms_receiver, index, from ); |
| 1733 | if (deliver == NULL) { |
| 1734 | D( "%s: could not create deliver PDUs for SMS index %d\n", |
| 1735 | __FUNCTION__, index ); |
| 1736 | break; |
| 1737 | } |
| 1738 | |
| 1739 | for (nn = 0; deliver[nn] != NULL; nn++) { |
| 1740 | if ( remote_call_sms( number, modem->base_port, deliver[nn] ) < 0 ) { |
| 1741 | D( "%s: could not send SMS PDU to remote emulator\n", |
| 1742 | __FUNCTION__ ); |
| 1743 | break; |
| 1744 | } |
| 1745 | } |
| 1746 | |
| 1747 | smspdu_free_list(deliver); |
| 1748 | } |
| 1749 | |
| 1750 | } while (0); |
| 1751 | |
| 1752 | if (pdu != NULL) |
| 1753 | smspdu_free(pdu); |
| 1754 | |
| 1755 | #elif 1 |
| 1756 | SmsAddressRec address; |
| 1757 | char number[16]; |
| 1758 | int numlen; |
| 1759 | int len = strlen(cmd); |
| 1760 | SmsPDU pdu; |
| 1761 | |
| 1762 | /* get rid of trailing escape */ |
| 1763 | if (len > 0 && cmd[len-1] == 0x1a) |
| 1764 | len -= 1; |
| 1765 | |
| 1766 | pdu = smspdu_create_from_hex( cmd, len ); |
| 1767 | if (pdu == NULL) { |
| 1768 | D("%s: invalid SMS PDU ?: '%s'\n", __FUNCTION__, cmd); |
| 1769 | return "+CMS ERROR: INVALID SMS PDU"; |
| 1770 | } |
| 1771 | if (smspdu_get_receiver_address(pdu, &address) < 0) { |
| 1772 | D("%s: could not get SMS receiver address from '%s'\n", |
| 1773 | __FUNCTION__, cmd); |
| 1774 | return "+CMS ERROR: BAD SMS RECEIVER ADDRESS"; |
| 1775 | } |
| 1776 | do { |
| 1777 | numlen = sms_address_to_str( &address, number, sizeof(number) ); |
| 1778 | if (numlen > sizeof(number)-1) |
| 1779 | break; |
| 1780 | |
| 1781 | number[numlen] = 0; |
| 1782 | if ( remote_number_string_to_port( number ) < 0 ) |
| 1783 | break; |
| 1784 | |
| 1785 | if ( remote_call_sms( number, modem->base_port, pdu ) < 0 ) |
| 1786 | { |
| 1787 | D("%s: could not send SMS PDU to remote emulator\n", |
| 1788 | __FUNCTION__); |
| 1789 | return "+CMS ERROR: NO EMULATOR RECEIVER"; |
| 1790 | } |
| 1791 | } while (0); |
| 1792 | #else |
| 1793 | fprintf(stderr, "SMS<< %s\n", cmd); |
| 1794 | SmsPDU pdu = smspdu_create_from_hex( cmd, strlen(cmd) ); |
| 1795 | if (pdu == NULL) { |
| 1796 | fprintf(stderr, "invalid SMS PDU ?: '%s'\n", cmd); |
| 1797 | } else { |
| 1798 | smspdu_dump(pdu, stderr); |
| 1799 | } |
| 1800 | #endif |
| 1801 | return "+CMGS: 0\rOK\r"; |
| 1802 | } |
| 1803 | |
| 1804 | static const char* |
| 1805 | handleChangeOrEnterPIN( const char* cmd, AModem modem ) |
| 1806 | { |
| 1807 | assert( !memcmp( cmd, "+CPIN=", 6 ) ); |
| 1808 | cmd += 6; |
| 1809 | |
| 1810 | switch (asimcard_get_status(modem->sim)) { |
| 1811 | case A_SIM_STATUS_ABSENT: |
| 1812 | return "+CME ERROR: SIM ABSENT"; |
| 1813 | |
| 1814 | case A_SIM_STATUS_NOT_READY: |
| 1815 | return "+CME ERROR: SIM NOT READY"; |
| 1816 | |
| 1817 | case A_SIM_STATUS_READY: |
| 1818 | /* this may be a request to change the PIN */ |
| 1819 | { |
| 1820 | if (strlen(cmd) == 9 && cmd[4] == ',') { |
| 1821 | char pin[5]; |
| 1822 | memcpy( pin, cmd, 4 ); pin[4] = 0; |
| 1823 | |
| 1824 | if ( !asimcard_check_pin( modem->sim, pin ) ) |
| 1825 | return "+CME ERROR: BAD PIN"; |
| 1826 | |
| 1827 | memcpy( pin, cmd+5, 4 ); |
| 1828 | asimcard_set_pin( modem->sim, pin ); |
| 1829 | return "+CPIN: READY"; |
| 1830 | } |
| 1831 | } |
| 1832 | break; |
| 1833 | |
| 1834 | case A_SIM_STATUS_PIN: /* waiting for PIN */ |
| 1835 | if ( asimcard_check_pin( modem->sim, cmd ) ) |
| 1836 | return "+CPIN: READY"; |
| 1837 | else |
| 1838 | return "+CME ERROR: BAD PIN"; |
| 1839 | |
| 1840 | case A_SIM_STATUS_PUK: |
| 1841 | if (strlen(cmd) == 9 && cmd[4] == ',') { |
| 1842 | char puk[5]; |
| 1843 | memcpy( puk, cmd, 4 ); |
| 1844 | puk[4] = 0; |
| 1845 | if ( asimcard_check_puk( modem->sim, puk, cmd+5 ) ) |
| 1846 | return "+CPIN: READY"; |
| 1847 | else |
| 1848 | return "+CME ERROR: BAD PUK"; |
| 1849 | } |
| 1850 | return "+CME ERROR: BAD PUK"; |
| 1851 | |
| 1852 | default: |
| 1853 | return "+CPIN: PH-NET PIN"; |
| 1854 | } |
| 1855 | |
| 1856 | return "+CME ERROR: BAD FORMAT"; |
| 1857 | } |
| 1858 | |
| 1859 | |
| 1860 | static const char* |
| 1861 | handleListCurrentCalls( const char* cmd, AModem modem ) |
| 1862 | { |
| 1863 | int nn; |
| 1864 | amodem_begin_line( modem ); |
| 1865 | for (nn = 0; nn < modem->call_count; nn++) { |
| 1866 | AVoiceCall vcall = modem->calls + nn; |
| 1867 | ACall call = &vcall->call; |
| 1868 | if (call->mode == A_CALL_VOICE) |
| 1869 | amodem_add_line( modem, "+CLCC: %d,%d,%d,%d,%d,\"%s\",%d\r\n", |
| 1870 | call->id, call->dir, call->state, call->mode, |
| 1871 | call->multi, call->number, 129 ); |
| 1872 | } |
| 1873 | return amodem_end_line( modem ); |
| 1874 | } |
| 1875 | |
Tim Baverstock | 622b8f4 | 2010-12-07 11:36:59 +0000 | [diff] [blame] | 1876 | /* Add a(n unsolicited) time response. |
| 1877 | * |
| 1878 | * retrieve the current time and zone in a format suitable |
The Android Open Source Project | 8b23a6c | 2009-03-03 19:30:32 -0800 | [diff] [blame] | 1879 | * for %CTZV: unsolicited message |
| 1880 | * "yy/mm/dd,hh:mm:ss(+/-)tz" |
| 1881 | * mm is 0-based |
| 1882 | * tz is in number of quarter-hours |
| 1883 | * |
| 1884 | * it seems reference-ril doesn't parse the comma (,) as anything else than a token |
| 1885 | * separator, so use a column (:) instead, the Java parsing code won't see a difference |
| 1886 | * |
| 1887 | */ |
Tim Baverstock | 622b8f4 | 2010-12-07 11:36:59 +0000 | [diff] [blame] | 1888 | static void |
| 1889 | amodem_addTimeUpdate( AModem modem ) |
The Android Open Source Project | 8b23a6c | 2009-03-03 19:30:32 -0800 | [diff] [blame] | 1890 | { |
| 1891 | time_t now = time(NULL); |
| 1892 | struct tm utc, local; |
| 1893 | long e_local, e_utc; |
| 1894 | long tzdiff; |
| 1895 | char tzname[64]; |
| 1896 | |
| 1897 | tzset(); |
| 1898 | |
| 1899 | utc = *gmtime( &now ); |
| 1900 | local = *localtime( &now ); |
| 1901 | |
| 1902 | e_local = local.tm_min + 60*(local.tm_hour + 24*local.tm_yday); |
| 1903 | e_utc = utc.tm_min + 60*(utc.tm_hour + 24*utc.tm_yday); |
| 1904 | |
| 1905 | if ( utc.tm_year < local.tm_year ) |
| 1906 | e_local += 24*60; |
| 1907 | else if ( utc.tm_year > local.tm_year ) |
| 1908 | e_utc += 24*60; |
| 1909 | |
| 1910 | tzdiff = e_local - e_utc; /* timezone offset in minutes */ |
| 1911 | |
| 1912 | /* retrieve a zoneinfo-compatible name for the host timezone |
| 1913 | */ |
| 1914 | { |
| 1915 | char* end = tzname + sizeof(tzname); |
| 1916 | char* p = bufprint_zoneinfo_timezone( tzname, end ); |
| 1917 | if (p >= end) |
| 1918 | strcpy(tzname, "Unknown/Unknown"); |
| 1919 | |
| 1920 | /* now replace every / in the timezone name by a "!" |
| 1921 | * that's because the code that reads the CTZV line is |
| 1922 | * dumb and treats a / as a field separator... |
| 1923 | */ |
| 1924 | p = tzname; |
| 1925 | while (1) { |
| 1926 | p = strchr(p, '/'); |
| 1927 | if (p == NULL) |
| 1928 | break; |
| 1929 | *p = '!'; |
| 1930 | p += 1; |
| 1931 | } |
| 1932 | } |
| 1933 | |
| 1934 | /* as a special extension, we append the name of the host's time zone to the |
| 1935 | * string returned with %CTZ. the system should contain special code to detect |
| 1936 | * and deal with this case (since it normally relied on the operator's country code |
| 1937 | * which is hard to simulate on a general-purpose computer |
| 1938 | */ |
Tim Baverstock | 622b8f4 | 2010-12-07 11:36:59 +0000 | [diff] [blame] | 1939 | amodem_add_line( modem, "%%CTZV: %02d/%02d/%02d:%02d:%02d:%02d%c%d:%d:%s\r\n", |
| 1940 | (utc.tm_year + 1900) % 100, utc.tm_mon + 1, utc.tm_mday, |
| 1941 | utc.tm_hour, utc.tm_min, utc.tm_sec, |
The Android Open Source Project | 8b23a6c | 2009-03-03 19:30:32 -0800 | [diff] [blame] | 1942 | (tzdiff >= 0) ? '+' : '-', (tzdiff >= 0 ? tzdiff : -tzdiff) / 15, |
| 1943 | (local.tm_isdst > 0), |
| 1944 | tzname ); |
| 1945 | } |
| 1946 | |
Tim Baverstock | 622b8f4 | 2010-12-07 11:36:59 +0000 | [diff] [blame] | 1947 | static const char* |
| 1948 | handleEndOfInit( const char* cmd, AModem modem ) |
| 1949 | { |
| 1950 | amodem_begin_line( modem ); |
| 1951 | amodem_addTimeUpdate( modem ); |
| 1952 | return amodem_end_line( modem ); |
| 1953 | } |
| 1954 | |
The Android Open Source Project | 8b23a6c | 2009-03-03 19:30:32 -0800 | [diff] [blame] | 1955 | |
| 1956 | static const char* |
| 1957 | handleListPDPContexts( const char* cmd, AModem modem ) |
| 1958 | { |
| 1959 | int nn; |
| 1960 | assert( !memcmp( cmd, "+CGACT?", 7 ) ); |
| 1961 | amodem_begin_line( modem ); |
| 1962 | for (nn = 0; nn < MAX_DATA_CONTEXTS; nn++) { |
| 1963 | ADataContext data = modem->data_contexts + nn; |
| 1964 | if (!data->active) |
| 1965 | continue; |
David 'Digit' Turner | d7f019d | 2011-02-04 12:30:54 +0100 | [diff] [blame] | 1966 | amodem_add_line( modem, "+CGACT: %d,%d\r\n", data->id, data->active ); |
The Android Open Source Project | 8b23a6c | 2009-03-03 19:30:32 -0800 | [diff] [blame] | 1967 | } |
| 1968 | return amodem_end_line( modem ); |
| 1969 | } |
| 1970 | |
| 1971 | static const char* |
| 1972 | handleDefinePDPContext( const char* cmd, AModem modem ) |
| 1973 | { |
| 1974 | assert( !memcmp( cmd, "+CGDCONT=", 9 ) ); |
| 1975 | cmd += 9; |
| 1976 | if (cmd[0] == '?') { |
David 'Digit' Turner | d7f019d | 2011-02-04 12:30:54 +0100 | [diff] [blame] | 1977 | /* +CGDCONT=? is used to query the ranges of supported PDP Contexts. |
| 1978 | * We only really support IP ones in the emulator, so don't try to |
| 1979 | * fake PPP ones. |
| 1980 | */ |
| 1981 | return "+CGDCONT: (1-1),\"IP\",,,(0-2),(0-4)\r\n"; |
The Android Open Source Project | 8b23a6c | 2009-03-03 19:30:32 -0800 | [diff] [blame] | 1982 | } else { |
| 1983 | /* template is +CGDCONT=<id>,"<type>","<apn>",,0,0 */ |
| 1984 | int id = cmd[0] - '1'; |
| 1985 | ADataType type; |
| 1986 | char apn[32]; |
| 1987 | ADataContext data; |
| 1988 | |
| 1989 | if ((unsigned)id > 3) |
| 1990 | goto BadCommand; |
| 1991 | |
| 1992 | if ( !memcmp( cmd+1, ",\"IP\",\"", 7 ) ) { |
| 1993 | type = A_DATA_IP; |
| 1994 | cmd += 8; |
| 1995 | } else if ( !memcmp( cmd+1, ",\"PPP\",\"", 8 ) ) { |
| 1996 | type = A_DATA_PPP; |
| 1997 | cmd += 9; |
| 1998 | } else |
| 1999 | goto BadCommand; |
| 2000 | |
| 2001 | { |
| 2002 | const char* p = strchr( cmd, '"' ); |
| 2003 | int len; |
| 2004 | if (p == NULL) |
| 2005 | goto BadCommand; |
| 2006 | len = (int)( p - cmd ); |
| 2007 | if (len > sizeof(apn)-1 ) |
| 2008 | len = sizeof(apn)-1; |
| 2009 | memcpy( apn, cmd, len ); |
| 2010 | apn[len] = 0; |
| 2011 | } |
| 2012 | |
| 2013 | data = modem->data_contexts + id; |
| 2014 | |
| 2015 | data->id = id + 1; |
| 2016 | data->active = 1; |
| 2017 | data->type = type; |
| 2018 | memcpy( data->apn, apn, sizeof(data->apn) ); |
| 2019 | } |
| 2020 | return NULL; |
| 2021 | BadCommand: |
| 2022 | return "ERROR: BAD COMMAND"; |
| 2023 | } |
| 2024 | |
David 'Digit' Turner | d7f019d | 2011-02-04 12:30:54 +0100 | [diff] [blame] | 2025 | static const char* |
| 2026 | handleQueryPDPContext( const char* cmd, AModem modem ) |
| 2027 | { |
| 2028 | int nn; |
| 2029 | amodem_begin_line(modem); |
| 2030 | for (nn = 0; nn < MAX_DATA_CONTEXTS; nn++) { |
| 2031 | ADataContext data = modem->data_contexts + nn; |
| 2032 | if (!data->active) |
| 2033 | continue; |
| 2034 | amodem_add_line( modem, "+CGDCONT: %d,\"%s\",\"%s\",\"%s\",0,0\r\n", |
| 2035 | data->id, |
| 2036 | data->type == A_DATA_IP ? "IP" : "PPP", |
| 2037 | data->apn, |
| 2038 | /* Note: For now, hard-code the IP address of our |
| 2039 | * network interface |
| 2040 | */ |
| 2041 | data->type == A_DATA_IP ? "10.0.2.15" : ""); |
| 2042 | } |
| 2043 | return amodem_end_line(modem); |
| 2044 | } |
The Android Open Source Project | 8b23a6c | 2009-03-03 19:30:32 -0800 | [diff] [blame] | 2045 | |
| 2046 | static const char* |
| 2047 | handleStartPDPContext( const char* cmd, AModem modem ) |
| 2048 | { |
| 2049 | /* XXX: TODO: handle PDP start appropriately */ |
The Android Open Source Project | 8b23a6c | 2009-03-03 19:30:32 -0800 | [diff] [blame] | 2050 | return NULL; |
| 2051 | } |
| 2052 | |
| 2053 | |
| 2054 | static void |
| 2055 | remote_voice_call_event( void* _vcall, int success ) |
| 2056 | { |
| 2057 | AVoiceCall vcall = _vcall; |
| 2058 | AModem modem = vcall->modem; |
| 2059 | |
| 2060 | /* NOTE: success only means we could send the "gsm in new" command |
| 2061 | * to the remote emulator, nothing more */ |
| 2062 | |
| 2063 | if (!success) { |
| 2064 | /* aargh, the remote emulator probably quitted at that point */ |
| 2065 | amodem_free_call(modem, vcall); |
| 2066 | amodem_send_calls_update(modem); |
| 2067 | } |
| 2068 | } |
| 2069 | |
| 2070 | |
| 2071 | static void |
| 2072 | voice_call_event( void* _vcall ) |
| 2073 | { |
| 2074 | AVoiceCall vcall = _vcall; |
| 2075 | ACall call = &vcall->call; |
| 2076 | |
| 2077 | switch (call->state) { |
| 2078 | case A_CALL_DIALING: |
| 2079 | call->state = A_CALL_ALERTING; |
| 2080 | |
| 2081 | if (vcall->is_remote) { |
| 2082 | if ( remote_call_dial( call->number, |
| 2083 | vcall->modem->base_port, |
| 2084 | remote_voice_call_event, vcall ) < 0 ) |
| 2085 | { |
| 2086 | /* we could not connect, probably because the corresponding |
| 2087 | * emulator is not running, so simply destroy this call. |
| 2088 | * XXX: should we send some sort of message to indicate BAD NUMBER ? */ |
| 2089 | /* it seems the Android code simply waits for changes in the list */ |
| 2090 | amodem_free_call( vcall->modem, vcall ); |
| 2091 | } |
| 2092 | } else { |
| 2093 | /* this is not a remote emulator number, so just simulate |
| 2094 | * a small ringing delay */ |
| 2095 | sys_timer_set( vcall->timer, sys_time_ms() + CALL_DELAY_ALERT, |
| 2096 | voice_call_event, vcall ); |
| 2097 | } |
| 2098 | break; |
| 2099 | |
| 2100 | case A_CALL_ALERTING: |
| 2101 | call->state = A_CALL_ACTIVE; |
| 2102 | break; |
| 2103 | |
| 2104 | default: |
| 2105 | assert( 0 && "unreachable event call state" ); |
| 2106 | } |
| 2107 | amodem_send_calls_update(vcall->modem); |
| 2108 | } |
| 2109 | |
Jaime Lopez | 1a00085 | 2010-07-21 18:03:58 -0700 | [diff] [blame] | 2110 | static int amodem_is_emergency( AModem modem, const char *number ) |
| 2111 | { |
| 2112 | int i; |
| 2113 | |
| 2114 | if (!number) return 0; |
| 2115 | for (i = 0; i < MAX_EMERGENCY_NUMBERS; i++) { |
| 2116 | if ( modem->emergency_numbers[i] && !strcmp( number, modem->emergency_numbers[i] )) break; |
| 2117 | } |
| 2118 | |
| 2119 | if (i < MAX_EMERGENCY_NUMBERS) return 1; |
| 2120 | |
| 2121 | return 0; |
| 2122 | } |
The Android Open Source Project | 8b23a6c | 2009-03-03 19:30:32 -0800 | [diff] [blame] | 2123 | |
| 2124 | static const char* |
| 2125 | handleDial( const char* cmd, AModem modem ) |
| 2126 | { |
| 2127 | AVoiceCall vcall = amodem_alloc_call( modem ); |
| 2128 | ACall call = &vcall->call; |
| 2129 | int len; |
| 2130 | |
| 2131 | if (call == NULL) |
| 2132 | return "ERROR: TOO MANY CALLS"; |
| 2133 | |
| 2134 | assert( cmd[0] == 'D' ); |
| 2135 | call->dir = A_CALL_OUTBOUND; |
| 2136 | call->state = A_CALL_DIALING; |
| 2137 | call->mode = A_CALL_VOICE; |
| 2138 | call->multi = 0; |
| 2139 | |
| 2140 | cmd += 1; |
| 2141 | len = strlen(cmd); |
| 2142 | if (len > 0 && cmd[len-1] == ';') |
| 2143 | len--; |
| 2144 | if (len >= sizeof(call->number)) |
| 2145 | len = sizeof(call->number)-1; |
| 2146 | |
Marc Petit-Huguenin | a1b379c | 2010-07-14 12:33:15 -0700 | [diff] [blame] | 2147 | /* Converts 4, 7, and 10 digits number to 11 digits */ |
| 2148 | if (len == 10 && !strncmp(cmd, PHONE_PREFIX+1, 6)) { |
| 2149 | memcpy( call->number, PHONE_PREFIX, 1 ); |
| 2150 | memcpy( call->number+1, cmd, len ); |
| 2151 | call->number[len+1] = 0; |
| 2152 | } else if (len == 7 && !strncmp(cmd, PHONE_PREFIX+4, 3)) { |
| 2153 | memcpy( call->number, PHONE_PREFIX, 4 ); |
| 2154 | memcpy( call->number+4, cmd, len ); |
| 2155 | call->number[len+4] = 0; |
| 2156 | } else if (len == 4) { |
| 2157 | memcpy( call->number, PHONE_PREFIX, 7 ); |
| 2158 | memcpy( call->number+7, cmd, len ); |
| 2159 | call->number[len+7] = 0; |
| 2160 | } else { |
| 2161 | memcpy( call->number, cmd, len ); |
| 2162 | call->number[len] = 0; |
| 2163 | } |
The Android Open Source Project | 8b23a6c | 2009-03-03 19:30:32 -0800 | [diff] [blame] | 2164 | |
Jaime Lopez | 1a00085 | 2010-07-21 18:03:58 -0700 | [diff] [blame] | 2165 | amodem_begin_line( modem ); |
| 2166 | if (amodem_is_emergency(modem, call->number)) { |
| 2167 | modem->in_emergency_mode = 1; |
| 2168 | amodem_add_line( modem, "+WSOS: 1" ); |
| 2169 | } |
The Android Open Source Project | 8b23a6c | 2009-03-03 19:30:32 -0800 | [diff] [blame] | 2170 | vcall->is_remote = (remote_number_string_to_port(call->number) > 0); |
| 2171 | |
| 2172 | vcall->timer = sys_timer_create(); |
| 2173 | sys_timer_set( vcall->timer, sys_time_ms() + CALL_DELAY_DIAL, |
| 2174 | voice_call_event, vcall ); |
| 2175 | |
Jaime Lopez | 1a00085 | 2010-07-21 18:03:58 -0700 | [diff] [blame] | 2176 | return amodem_end_line( modem ); |
The Android Open Source Project | 8b23a6c | 2009-03-03 19:30:32 -0800 | [diff] [blame] | 2177 | } |
| 2178 | |
| 2179 | |
| 2180 | static const char* |
| 2181 | handleAnswer( const char* cmd, AModem modem ) |
| 2182 | { |
| 2183 | int nn; |
| 2184 | for (nn = 0; nn < modem->call_count; nn++) { |
| 2185 | AVoiceCall vcall = modem->calls + nn; |
| 2186 | ACall call = &vcall->call; |
| 2187 | |
| 2188 | if (cmd[0] == 'A') { |
| 2189 | if (call->state == A_CALL_INCOMING) { |
| 2190 | acall_set_state( vcall, A_CALL_ACTIVE ); |
| 2191 | } |
| 2192 | else if (call->state == A_CALL_ACTIVE) { |
| 2193 | acall_set_state( vcall, A_CALL_HELD ); |
| 2194 | } |
| 2195 | } else if (cmd[0] == 'H') { |
| 2196 | /* ATH: hangup, since user is busy */ |
| 2197 | if (call->state == A_CALL_INCOMING) { |
| 2198 | amodem_free_call( modem, vcall ); |
| 2199 | break; |
| 2200 | } |
| 2201 | } |
| 2202 | } |
| 2203 | return NULL; |
| 2204 | } |
| 2205 | |
Tim Baverstock | 622b8f4 | 2010-12-07 11:36:59 +0000 | [diff] [blame] | 2206 | int android_snapshot_update_time = 1; |
| 2207 | int android_snapshot_update_time_request = 0; |
Tim Baverstock | 622b8f4 | 2010-12-07 11:36:59 +0000 | [diff] [blame] | 2208 | |
| 2209 | static const char* |
| 2210 | handleSignalStrength( const char* cmd, AModem modem ) |
| 2211 | { |
Tim Baverstock | 622b8f4 | 2010-12-07 11:36:59 +0000 | [diff] [blame] | 2212 | amodem_begin_line( modem ); |
David 'Digit' Turner | e5af8a2 | 2011-02-24 16:48:19 +0100 | [diff] [blame] | 2213 | |
Tim Baverstock | 622b8f4 | 2010-12-07 11:36:59 +0000 | [diff] [blame] | 2214 | /* Sneak time updates into the SignalStrength request, because it's periodic. |
| 2215 | * Ideally, we'd be able to prod the guest into asking immediately on restore |
| 2216 | * from snapshot, but that'd require a driver. |
| 2217 | */ |
| 2218 | if ( android_snapshot_update_time && android_snapshot_update_time_request ) { |
| 2219 | amodem_addTimeUpdate( modem ); |
| 2220 | android_snapshot_update_time_request = 0; |
| 2221 | } |
David 'Digit' Turner | e5af8a2 | 2011-02-24 16:48:19 +0100 | [diff] [blame] | 2222 | |
Tim Baverstock | 622b8f4 | 2010-12-07 11:36:59 +0000 | [diff] [blame] | 2223 | // rssi = 0 (<-113dBm) 1 (<-111) 2-30 (<-109--53) 31 (>=-51) 99 (?!) |
| 2224 | // ber (bit error rate) - always 99 (unknown), apparently. |
Tim Baverstock | 4c6b10a | 2010-12-15 17:31:13 +0000 | [diff] [blame] | 2225 | // TODO: return 99 if modem->radio_state==A_RADIO_STATE_OFF, once radio_state is in snapshot. |
| 2226 | int rssi = modem->rssi; |
| 2227 | int ber = modem->ber; |
| 2228 | rssi = (0 > rssi && rssi > 31) ? 99 : rssi ; |
| 2229 | ber = (0 > ber && ber > 7 ) ? 99 : ber; |
Uma Maheswari Ramalingam | e9bd6b6 | 2012-08-09 12:12:22 -0700 | [diff] [blame] | 2230 | amodem_add_line( modem, "+CSQ: %i,%i,85,130,90,6,4,25,9,50,68,12\r\n", rssi, ber ); |
Tim Baverstock | 622b8f4 | 2010-12-07 11:36:59 +0000 | [diff] [blame] | 2231 | return amodem_end_line( modem ); |
| 2232 | } |
| 2233 | |
The Android Open Source Project | 8b23a6c | 2009-03-03 19:30:32 -0800 | [diff] [blame] | 2234 | static const char* |
| 2235 | handleHangup( const char* cmd, AModem modem ) |
| 2236 | { |
| 2237 | if ( !memcmp(cmd, "+CHLD=", 6) ) { |
| 2238 | int nn; |
| 2239 | cmd += 6; |
| 2240 | switch (cmd[0]) { |
| 2241 | case '0': /* release all held, and set busy for waiting calls */ |
| 2242 | for (nn = 0; nn < modem->call_count; nn++) { |
| 2243 | AVoiceCall vcall = modem->calls + nn; |
| 2244 | ACall call = &vcall->call; |
| 2245 | if (call->mode != A_CALL_VOICE) |
| 2246 | continue; |
| 2247 | if (call->state == A_CALL_HELD || |
| 2248 | call->state == A_CALL_WAITING || |
| 2249 | call->state == A_CALL_INCOMING) { |
| 2250 | amodem_free_call(modem, vcall); |
| 2251 | nn--; |
| 2252 | } |
| 2253 | } |
| 2254 | break; |
| 2255 | |
| 2256 | case '1': |
| 2257 | if (cmd[1] == 0) { /* release all active, accept held one */ |
| 2258 | for (nn = 0; nn < modem->call_count; nn++) { |
| 2259 | AVoiceCall vcall = modem->calls + nn; |
| 2260 | ACall call = &vcall->call; |
| 2261 | if (call->mode != A_CALL_VOICE) |
| 2262 | continue; |
| 2263 | if (call->state == A_CALL_ACTIVE) { |
| 2264 | amodem_free_call(modem, vcall); |
| 2265 | nn--; |
| 2266 | } |
| 2267 | else if (call->state == A_CALL_HELD || |
| 2268 | call->state == A_CALL_WAITING) { |
| 2269 | acall_set_state( vcall, A_CALL_ACTIVE ); |
| 2270 | } |
| 2271 | } |
| 2272 | } else { /* release specific call */ |
| 2273 | int id = cmd[1] - '0'; |
| 2274 | AVoiceCall vcall = amodem_find_call( modem, id ); |
| 2275 | if (vcall != NULL) |
| 2276 | amodem_free_call( modem, vcall ); |
| 2277 | } |
| 2278 | break; |
| 2279 | |
| 2280 | case '2': |
| 2281 | if (cmd[1] == 0) { /* place all active on hold, accept held or waiting one */ |
| 2282 | for (nn = 0; nn < modem->call_count; nn++) { |
| 2283 | AVoiceCall vcall = modem->calls + nn; |
| 2284 | ACall call = &vcall->call; |
| 2285 | if (call->mode != A_CALL_VOICE) |
| 2286 | continue; |
| 2287 | if (call->state == A_CALL_ACTIVE) { |
| 2288 | acall_set_state( vcall, A_CALL_HELD ); |
| 2289 | } |
| 2290 | else if (call->state == A_CALL_HELD || |
| 2291 | call->state == A_CALL_WAITING) { |
| 2292 | acall_set_state( vcall, A_CALL_ACTIVE ); |
| 2293 | } |
| 2294 | } |
| 2295 | } else { /* place all active on hold, except a specific one */ |
| 2296 | int id = cmd[1] - '0'; |
| 2297 | for (nn = 0; nn < modem->call_count; nn++) { |
| 2298 | AVoiceCall vcall = modem->calls + nn; |
| 2299 | ACall call = &vcall->call; |
| 2300 | if (call->mode != A_CALL_VOICE) |
| 2301 | continue; |
| 2302 | if (call->state == A_CALL_ACTIVE && call->id != id) { |
| 2303 | acall_set_state( vcall, A_CALL_HELD ); |
| 2304 | } |
| 2305 | } |
| 2306 | } |
| 2307 | break; |
| 2308 | |
| 2309 | case '3': /* add a held call to the conversation */ |
| 2310 | for (nn = 0; nn < modem->call_count; nn++) { |
| 2311 | AVoiceCall vcall = modem->calls + nn; |
| 2312 | ACall call = &vcall->call; |
| 2313 | if (call->mode != A_CALL_VOICE) |
| 2314 | continue; |
| 2315 | if (call->state == A_CALL_HELD) { |
| 2316 | acall_set_state( vcall, A_CALL_ACTIVE ); |
| 2317 | break; |
| 2318 | } |
| 2319 | } |
| 2320 | break; |
| 2321 | |
| 2322 | case '4': /* connect the two calls */ |
| 2323 | for (nn = 0; nn < modem->call_count; nn++) { |
| 2324 | AVoiceCall vcall = modem->calls + nn; |
| 2325 | ACall call = &vcall->call; |
| 2326 | if (call->mode != A_CALL_VOICE) |
| 2327 | continue; |
| 2328 | if (call->state == A_CALL_HELD) { |
| 2329 | acall_set_state( vcall, A_CALL_ACTIVE ); |
| 2330 | break; |
| 2331 | } |
| 2332 | } |
| 2333 | break; |
| 2334 | } |
| 2335 | } |
| 2336 | else |
| 2337 | return "ERROR: BAD COMMAND"; |
| 2338 | |
| 2339 | return NULL; |
| 2340 | } |
| 2341 | |
| 2342 | |
| 2343 | /* a function used to deal with a non-trivial request */ |
| 2344 | typedef const char* (*ResponseHandler)(const char* cmd, AModem modem); |
| 2345 | |
| 2346 | static const struct { |
| 2347 | const char* cmd; /* command coming from libreference-ril.so, if first |
| 2348 | character is '!', then the rest is a prefix only */ |
| 2349 | |
| 2350 | const char* answer; /* default answer, NULL if needs specific handling or |
| 2351 | if OK is good enough */ |
| 2352 | |
| 2353 | ResponseHandler handler; /* specific handler, ignored if 'answer' is not NULL, |
| 2354 | NULL if OK is good enough */ |
| 2355 | } sDefaultResponses[] = |
| 2356 | { |
| 2357 | /* see onRadioPowerOn() */ |
| 2358 | { "%CPHS=1", NULL, NULL }, |
| 2359 | { "%CTZV=1", NULL, NULL }, |
| 2360 | |
| 2361 | /* see onSIMReady() */ |
| 2362 | { "+CSMS=1", "+CSMS: 1, 1, 1", NULL }, |
| 2363 | { "+CNMI=1,2,2,1,1", NULL, NULL }, |
| 2364 | |
| 2365 | /* see requestRadioPower() */ |
| 2366 | { "+CFUN=0", NULL, handleRadioPower }, |
| 2367 | { "+CFUN=1", NULL, handleRadioPower }, |
| 2368 | |
Jaime Lopez | 1a00085 | 2010-07-21 18:03:58 -0700 | [diff] [blame] | 2369 | { "+CTEC=?", "+CTEC: 0,1,2,3", NULL }, /* Query available Techs */ |
| 2370 | { "!+CTEC", NULL, handleTech }, /* Set/get current Technology and preferred mode */ |
| 2371 | |
| 2372 | { "+WRMP=?", "+WRMP: 0,1,2", NULL }, /* Query Roam Preference */ |
| 2373 | { "!+WRMP", NULL, handleRoamPref }, /* Set/get Roam Preference */ |
| 2374 | |
| 2375 | { "+CCSS=?", "+CTEC: 0,1", NULL }, /* Query available subscription sources */ |
| 2376 | { "!+CCSS", NULL, handleSubscriptionSource }, /* Set/Get current subscription source */ |
| 2377 | |
| 2378 | { "+WSOS=?", "+WSOS: 0", NULL}, /* Query supported +WSOS values */ |
| 2379 | { "!+WSOS=", NULL, handleEmergencyMode }, |
| 2380 | |
| 2381 | { "+WPRL?", NULL, handlePrlVersion }, /* Query the current PRL version */ |
| 2382 | |
The Android Open Source Project | 8b23a6c | 2009-03-03 19:30:32 -0800 | [diff] [blame] | 2383 | /* see requestOrSendPDPContextList() */ |
David 'Digit' Turner | d7f019d | 2011-02-04 12:30:54 +0100 | [diff] [blame] | 2384 | { "+CGACT?", NULL, handleListPDPContexts }, |
The Android Open Source Project | 8b23a6c | 2009-03-03 19:30:32 -0800 | [diff] [blame] | 2385 | |
| 2386 | /* see requestOperator() */ |
| 2387 | { "+COPS=3,0;+COPS?;+COPS=3,1;+COPS?;+COPS=3,2;+COPS?", NULL, handleRequestOperator }, |
| 2388 | |
| 2389 | /* see requestQueryNetworkSelectionMode() */ |
| 2390 | { "!+COPS", NULL, handleOperatorSelection }, |
| 2391 | |
| 2392 | /* see requestGetCurrentCalls() */ |
| 2393 | { "+CLCC", NULL, handleListCurrentCalls }, |
| 2394 | |
| 2395 | /* see requestWriteSmsToSim() */ |
| 2396 | { "!+CMGW=", NULL, handleSendSMStoSIM }, |
| 2397 | |
| 2398 | /* see requestHangup() */ |
| 2399 | { "!+CHLD=", NULL, handleHangup }, |
| 2400 | |
| 2401 | /* see requestSignalStrength() */ |
Tim Baverstock | 622b8f4 | 2010-12-07 11:36:59 +0000 | [diff] [blame] | 2402 | { "+CSQ", NULL, handleSignalStrength }, |
The Android Open Source Project | 8b23a6c | 2009-03-03 19:30:32 -0800 | [diff] [blame] | 2403 | |
| 2404 | /* see requestRegistrationState() */ |
| 2405 | { "!+CREG", NULL, handleNetworkRegistration }, |
| 2406 | { "!+CGREG", NULL, handleNetworkRegistration }, |
| 2407 | |
| 2408 | /* see requestSendSMS() */ |
| 2409 | { "!+CMGS=", NULL, handleSendSMS }, |
| 2410 | |
| 2411 | /* see requestSetupDefaultPDP() */ |
| 2412 | { "%CPRIM=\"GMM\",\"CONFIG MULTISLOT_CLASS=<10>\"", NULL, NULL }, |
| 2413 | { "%DATA=2,\"UART\",1,,\"SER\",\"UART\",0", NULL, NULL }, |
| 2414 | |
| 2415 | { "!+CGDCONT=", NULL, handleDefinePDPContext }, |
David 'Digit' Turner | d7f019d | 2011-02-04 12:30:54 +0100 | [diff] [blame] | 2416 | { "+CGDCONT?", NULL, handleQueryPDPContext }, |
The Android Open Source Project | 8b23a6c | 2009-03-03 19:30:32 -0800 | [diff] [blame] | 2417 | |
| 2418 | { "+CGQREQ=1", NULL, NULL }, |
| 2419 | { "+CGQMIN=1", NULL, NULL }, |
| 2420 | { "+CGEREP=1,0", NULL, NULL }, |
| 2421 | { "+CGACT=1,0", NULL, NULL }, |
| 2422 | { "D*99***1#", NULL, handleStartPDPContext }, |
| 2423 | |
| 2424 | /* see requestDial() */ |
| 2425 | { "!D", NULL, handleDial }, /* the code says that success/error is ignored, the call state will |
| 2426 | be polled through +CLCC instead */ |
| 2427 | |
| 2428 | /* see requestSMSAcknowledge() */ |
| 2429 | { "+CNMA=1", NULL, NULL }, |
| 2430 | { "+CNMA=2", NULL, NULL }, |
| 2431 | |
| 2432 | /* see requestSIM_IO() */ |
| 2433 | { "!+CRSM=", NULL, handleSIM_IO }, |
| 2434 | |
| 2435 | /* see onRequest() */ |
| 2436 | { "+CHLD=0", NULL, handleHangup }, |
| 2437 | { "+CHLD=1", NULL, handleHangup }, |
| 2438 | { "+CHLD=2", NULL, handleHangup }, |
| 2439 | { "+CHLD=3", NULL, handleHangup }, |
| 2440 | { "A", NULL, handleAnswer }, /* answer the call */ |
| 2441 | { "H", NULL, handleAnswer }, /* user is busy */ |
| 2442 | { "!+VTS=", NULL, handleSetDialTone }, |
| 2443 | { "+CIMI", OPERATOR_HOME_MCCMNC "000000000", NULL }, /* request internation subscriber identification number */ |
| 2444 | { "+CGSN", "000000000000000", NULL }, /* request model version */ |
| 2445 | { "+CUSD=2",NULL, NULL }, /* Cancel USSD */ |
| 2446 | { "+COPS=0", NULL, handleOperatorSelection }, /* set network selection to automatic */ |
| 2447 | { "!+CMGD=", NULL, handleDeleteSMSonSIM }, /* delete SMS on SIM */ |
| 2448 | { "!+CPIN=", NULL, handleChangeOrEnterPIN }, |
| 2449 | |
| 2450 | /* see getSIMStatus() */ |
| 2451 | { "+CPIN?", NULL, handleSIMStatusReq }, |
| 2452 | { "+CNMI?", "+CNMI: 1,2,2,1,1", NULL }, |
| 2453 | |
| 2454 | /* see isRadioOn() */ |
| 2455 | { "+CFUN?", NULL, handleRadioPowerReq }, |
| 2456 | |
| 2457 | /* see initializeCallback() */ |
| 2458 | { "E0Q0V1", NULL, NULL }, |
| 2459 | { "S0=0", NULL, NULL }, |
| 2460 | { "+CMEE=1", NULL, NULL }, |
The Android Open Source Project | 8b23a6c | 2009-03-03 19:30:32 -0800 | [diff] [blame] | 2461 | { "+CCWA=1", NULL, NULL }, |
| 2462 | { "+CMOD=0", NULL, NULL }, |
| 2463 | { "+CMUT=0", NULL, NULL }, |
| 2464 | { "+CSSN=0,1", NULL, NULL }, |
| 2465 | { "+COLP=0", NULL, NULL }, |
| 2466 | { "+CSCS=\"HEX\"", NULL, NULL }, |
| 2467 | { "+CUSD=1", NULL, NULL }, |
| 2468 | { "+CGEREP=1,0", NULL, NULL }, |
| 2469 | { "+CMGF=0", NULL, handleEndOfInit }, /* now is a goof time to send the current tme and timezone */ |
| 2470 | { "%CPI=3", NULL, NULL }, |
| 2471 | { "%CSTAT=1", NULL, NULL }, |
| 2472 | |
| 2473 | /* end of list */ |
| 2474 | {NULL, NULL, NULL} |
| 2475 | }; |
| 2476 | |
| 2477 | |
| 2478 | #define REPLY(str) do { const char* s = (str); R(">> %s\n", quote(s)); return s; } while (0) |
| 2479 | |
| 2480 | const char* amodem_send( AModem modem, const char* cmd ) |
| 2481 | { |
| 2482 | const char* answer; |
| 2483 | |
| 2484 | if ( modem->wait_sms != 0 ) { |
| 2485 | modem->wait_sms = 0; |
| 2486 | R( "SMS<< %s\n", quote(cmd) ); |
| 2487 | answer = handleSendSMSText( cmd, modem ); |
| 2488 | REPLY(answer); |
| 2489 | } |
| 2490 | |
| 2491 | /* everything that doesn't start with 'AT' is not a command, right ? */ |
| 2492 | if ( cmd[0] != 'A' || cmd[1] != 'T' || cmd[2] == 0 ) { |
| 2493 | /* R( "-- %s\n", quote(cmd) ); */ |
| 2494 | return NULL; |
| 2495 | } |
| 2496 | R( "<< %s\n", quote(cmd) ); |
| 2497 | |
| 2498 | cmd += 2; |
| 2499 | |
| 2500 | /* TODO: implement command handling */ |
| 2501 | { |
| 2502 | int nn, found = 0; |
| 2503 | |
| 2504 | for (nn = 0; ; nn++) { |
| 2505 | const char* scmd = sDefaultResponses[nn].cmd; |
| 2506 | |
| 2507 | if (!scmd) /* end of list */ |
| 2508 | break; |
| 2509 | |
| 2510 | if (scmd[0] == '!') { /* prefix match */ |
| 2511 | int len = strlen(++scmd); |
| 2512 | |
| 2513 | if ( !memcmp( scmd, cmd, len ) ) { |
| 2514 | found = 1; |
| 2515 | break; |
| 2516 | } |
| 2517 | } else { /* full match */ |
| 2518 | if ( !strcmp( scmd, cmd ) ) { |
| 2519 | found = 1; |
| 2520 | break; |
| 2521 | } |
| 2522 | } |
| 2523 | } |
| 2524 | |
| 2525 | if ( !found ) |
| 2526 | { |
| 2527 | D( "** UNSUPPORTED COMMAND **\n" ); |
| 2528 | REPLY( "ERROR: UNSUPPORTED" ); |
| 2529 | } |
| 2530 | else |
| 2531 | { |
| 2532 | const char* answer = sDefaultResponses[nn].answer; |
| 2533 | ResponseHandler handler = sDefaultResponses[nn].handler; |
| 2534 | |
| 2535 | if ( answer != NULL ) { |
| 2536 | REPLY( amodem_printf( modem, "%s\rOK", answer ) ); |
| 2537 | } |
| 2538 | |
| 2539 | if (handler == NULL) { |
| 2540 | REPLY( "OK" ); |
| 2541 | } |
| 2542 | |
| 2543 | answer = handler( cmd, modem ); |
| 2544 | if (answer == NULL) |
| 2545 | REPLY( "OK" ); |
| 2546 | |
| 2547 | if ( !memcmp( answer, "> ", 2 ) || |
| 2548 | !memcmp( answer, "ERROR", 5 ) || |
| 2549 | !memcmp( answer, "+CME ERROR", 6 ) ) |
| 2550 | { |
| 2551 | REPLY( answer ); |
| 2552 | } |
| 2553 | |
| 2554 | if (answer != modem->out_buff) |
| 2555 | REPLY( amodem_printf( modem, "%s\rOK", answer ) ); |
| 2556 | |
| 2557 | strcat( modem->out_buff, "\rOK" ); |
| 2558 | REPLY( answer ); |
| 2559 | } |
| 2560 | } |
| 2561 | } |