dom/system/gonk/ril_worker.js

Thu, 22 Jan 2015 13:21:57 +0100

author
Michael Schloh von Bennewitz <michael@schloh.com>
date
Thu, 22 Jan 2015 13:21:57 +0100
branch
TOR_BUG_9701
changeset 15
b8a032363ba2
permissions
-rw-r--r--

Incorporate requested changes from Mozilla in review:
https://bugzilla.mozilla.org/show_bug.cgi?id=1123480#c6

michael@0 1 /* Copyright 2012 Mozilla Foundation and Mozilla contributors
michael@0 2 *
michael@0 3 * Licensed under the Apache License, Version 2.0 (the "License");
michael@0 4 * you may not use this file except in compliance with the License.
michael@0 5 * You may obtain a copy of the License at
michael@0 6 *
michael@0 7 * http://www.apache.org/licenses/LICENSE-2.0
michael@0 8 *
michael@0 9 * Unless required by applicable law or agreed to in writing, software
michael@0 10 * distributed under the License is distributed on an "AS IS" BASIS,
michael@0 11 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
michael@0 12 * See the License for the specific language governing permissions and
michael@0 13 * limitations under the License.
michael@0 14 */
michael@0 15
michael@0 16 /**
michael@0 17 * This file implements the RIL worker thread. It communicates with
michael@0 18 * the main thread to provide a high-level API to the phone's RIL
michael@0 19 * stack, and with the RIL IPC thread to communicate with the RIL
michael@0 20 * device itself. These communication channels use message events as
michael@0 21 * known from Web Workers:
michael@0 22 *
michael@0 23 * - postMessage()/"message" events for main thread communication
michael@0 24 *
michael@0 25 * - postRILMessage()/"RILMessageEvent" events for RIL IPC thread
michael@0 26 * communication.
michael@0 27 *
michael@0 28 * The two main objects in this file represent individual parts of this
michael@0 29 * communication chain:
michael@0 30 *
michael@0 31 * - RILMessageEvent -> Buf -> RIL -> postMessage() -> nsIRadioInterfaceLayer
michael@0 32 * - nsIRadioInterfaceLayer -> postMessage() -> RIL -> Buf -> postRILMessage()
michael@0 33 *
michael@0 34 * Note: The code below is purposely lean on abstractions to be as lean in
michael@0 35 * terms of object allocations. As a result, it may look more like C than
michael@0 36 * JavaScript, and that's intended.
michael@0 37 */
michael@0 38
michael@0 39 "use strict";
michael@0 40
michael@0 41 importScripts("ril_consts.js");
michael@0 42 importScripts("resource://gre/modules/workers/require.js");
michael@0 43
michael@0 44 // set to true in ril_consts.js to see debug messages
michael@0 45 let DEBUG = DEBUG_WORKER;
michael@0 46 let GLOBAL = this;
michael@0 47
michael@0 48 if (!this.debug) {
michael@0 49 // Debugging stub that goes nowhere.
michael@0 50 this.debug = function debug(message) {
michael@0 51 dump("RIL Worker: " + message + "\n");
michael@0 52 };
michael@0 53 }
michael@0 54
michael@0 55 let RIL_CELLBROADCAST_DISABLED;
michael@0 56 let RIL_CLIR_MODE;
michael@0 57 let RIL_EMERGENCY_NUMBERS;
michael@0 58 const DEFAULT_EMERGENCY_NUMBERS = ["112", "911"];
michael@0 59
michael@0 60 // Timeout value for emergency callback mode.
michael@0 61 const EMERGENCY_CB_MODE_TIMEOUT_MS = 300000; // 5 mins = 300000 ms.
michael@0 62
michael@0 63 const ICC_MAX_LINEAR_FIXED_RECORDS = 0xfe;
michael@0 64
michael@0 65 // MMI match groups
michael@0 66 const MMI_MATCH_GROUP_FULL_MMI = 1;
michael@0 67 const MMI_MATCH_GROUP_MMI_PROCEDURE = 2;
michael@0 68 const MMI_MATCH_GROUP_SERVICE_CODE = 3;
michael@0 69 const MMI_MATCH_GROUP_SIA = 5;
michael@0 70 const MMI_MATCH_GROUP_SIB = 7;
michael@0 71 const MMI_MATCH_GROUP_SIC = 9;
michael@0 72 const MMI_MATCH_GROUP_PWD_CONFIRM = 11;
michael@0 73 const MMI_MATCH_GROUP_DIALING_NUMBER = 12;
michael@0 74
michael@0 75 const MMI_MAX_LENGTH_SHORT_CODE = 2;
michael@0 76
michael@0 77 const MMI_END_OF_USSD = "#";
michael@0 78
michael@0 79 // Should match the value we set in dom/telephony/TelephonyCommon.h
michael@0 80 const OUTGOING_PLACEHOLDER_CALL_INDEX = 0xffffffff;
michael@0 81
michael@0 82 let RILQUIRKS_CALLSTATE_EXTRA_UINT32;
michael@0 83 // This may change at runtime since in RIL v6 and later, we get the version
michael@0 84 // number via the UNSOLICITED_RIL_CONNECTED parcel.
michael@0 85 let RILQUIRKS_V5_LEGACY;
michael@0 86 let RILQUIRKS_REQUEST_USE_DIAL_EMERGENCY_CALL;
michael@0 87 let RILQUIRKS_SIM_APP_STATE_EXTRA_FIELDS;
michael@0 88 // Needed for call-waiting on Peak device
michael@0 89 let RILQUIRKS_EXTRA_UINT32_2ND_CALL;
michael@0 90 // On the emulator we support querying the number of lock retries
michael@0 91 let RILQUIRKS_HAVE_QUERY_ICC_LOCK_RETRY_COUNT;
michael@0 92
michael@0 93 // Ril quirk to Send STK Profile Download
michael@0 94 let RILQUIRKS_SEND_STK_PROFILE_DOWNLOAD;
michael@0 95
michael@0 96 // Ril quirk to attach data registration on demand.
michael@0 97 let RILQUIRKS_DATA_REGISTRATION_ON_DEMAND;
michael@0 98
michael@0 99 function BufObject(aContext) {
michael@0 100 this.context = aContext;
michael@0 101 }
michael@0 102 BufObject.prototype = {
michael@0 103 context: null,
michael@0 104
michael@0 105 mToken: 0,
michael@0 106 mTokenRequestMap: null,
michael@0 107
michael@0 108 init: function() {
michael@0 109 this._init();
michael@0 110
michael@0 111 // This gets incremented each time we send out a parcel.
michael@0 112 this.mToken = 1;
michael@0 113
michael@0 114 // Maps tokens we send out with requests to the request type, so that
michael@0 115 // when we get a response parcel back, we know what request it was for.
michael@0 116 this.mTokenRequestMap = new Map();
michael@0 117 },
michael@0 118
michael@0 119 /**
michael@0 120 * Process one parcel.
michael@0 121 */
michael@0 122 processParcel: function() {
michael@0 123 let response_type = this.readInt32();
michael@0 124
michael@0 125 let request_type, options;
michael@0 126 if (response_type == RESPONSE_TYPE_SOLICITED) {
michael@0 127 let token = this.readInt32();
michael@0 128 let error = this.readInt32();
michael@0 129
michael@0 130 options = this.mTokenRequestMap.get(token);
michael@0 131 if (!options) {
michael@0 132 if (DEBUG) {
michael@0 133 this.context.debug("Suspicious uninvited request found: " +
michael@0 134 token + ". Ignored!");
michael@0 135 }
michael@0 136 return;
michael@0 137 }
michael@0 138
michael@0 139 this.mTokenRequestMap.delete(token);
michael@0 140 request_type = options.rilRequestType;
michael@0 141
michael@0 142 options.rilRequestError = error;
michael@0 143 if (DEBUG) {
michael@0 144 this.context.debug("Solicited response for request type " + request_type +
michael@0 145 ", token " + token + ", error " + error);
michael@0 146 }
michael@0 147 } else if (response_type == RESPONSE_TYPE_UNSOLICITED) {
michael@0 148 request_type = this.readInt32();
michael@0 149 if (DEBUG) {
michael@0 150 this.context.debug("Unsolicited response for request type " + request_type);
michael@0 151 }
michael@0 152 } else {
michael@0 153 if (DEBUG) {
michael@0 154 this.context.debug("Unknown response type: " + response_type);
michael@0 155 }
michael@0 156 return;
michael@0 157 }
michael@0 158
michael@0 159 this.context.RIL.handleParcel(request_type, this.readAvailable, options);
michael@0 160 },
michael@0 161
michael@0 162 /**
michael@0 163 * Start a new outgoing parcel.
michael@0 164 *
michael@0 165 * @param type
michael@0 166 * Integer specifying the request type.
michael@0 167 * @param options [optional]
michael@0 168 * Object containing information about the request, e.g. the
michael@0 169 * original main thread message object that led to the RIL request.
michael@0 170 */
michael@0 171 newParcel: function(type, options) {
michael@0 172 if (DEBUG) this.context.debug("New outgoing parcel of type " + type);
michael@0 173
michael@0 174 // We're going to leave room for the parcel size at the beginning.
michael@0 175 this.outgoingIndex = this.PARCEL_SIZE_SIZE;
michael@0 176 this.writeInt32(type);
michael@0 177 this.writeInt32(this.mToken);
michael@0 178
michael@0 179 if (!options) {
michael@0 180 options = {};
michael@0 181 }
michael@0 182 options.rilRequestType = type;
michael@0 183 options.rilRequestError = null;
michael@0 184 this.mTokenRequestMap.set(this.mToken, options);
michael@0 185 this.mToken++;
michael@0 186 return this.mToken;
michael@0 187 },
michael@0 188
michael@0 189 simpleRequest: function(type, options) {
michael@0 190 this.newParcel(type, options);
michael@0 191 this.sendParcel();
michael@0 192 },
michael@0 193
michael@0 194 onSendParcel: function(parcel) {
michael@0 195 postRILMessage(this.context.clientId, parcel);
michael@0 196 }
michael@0 197 };
michael@0 198
michael@0 199 (function() {
michael@0 200 let base = require("resource://gre/modules/workers/worker_buf.js").Buf;
michael@0 201 for (let p in base) {
michael@0 202 BufObject.prototype[p] = base[p];
michael@0 203 }
michael@0 204 })();
michael@0 205
michael@0 206 /**
michael@0 207 * The RIL state machine.
michael@0 208 *
michael@0 209 * This object communicates with rild via parcels and with the main thread
michael@0 210 * via post messages. It maintains state about the radio, ICC, calls, etc.
michael@0 211 * and acts upon state changes accordingly.
michael@0 212 */
michael@0 213 function RilObject(aContext) {
michael@0 214 this.context = aContext;
michael@0 215
michael@0 216 this.currentCalls = {};
michael@0 217 this.currentConference = {state: null, participants: {}};
michael@0 218 this.currentDataCalls = {};
michael@0 219 this._pendingSentSmsMap = {};
michael@0 220 this.pendingNetworkType = {};
michael@0 221 this._receivedSmsCbPagesMap = {};
michael@0 222
michael@0 223 // Init properties that are only initialized once.
michael@0 224 this.v5Legacy = RILQUIRKS_V5_LEGACY;
michael@0 225 this.cellBroadcastDisabled = RIL_CELLBROADCAST_DISABLED;
michael@0 226 this.clirMode = RIL_CLIR_MODE;
michael@0 227 }
michael@0 228 RilObject.prototype = {
michael@0 229 context: null,
michael@0 230
michael@0 231 v5Legacy: null,
michael@0 232
michael@0 233 /**
michael@0 234 * Valid calls.
michael@0 235 */
michael@0 236 currentCalls: null,
michael@0 237
michael@0 238 /**
michael@0 239 * Existing conference call and its participants.
michael@0 240 */
michael@0 241 currentConference: null,
michael@0 242
michael@0 243 /**
michael@0 244 * Existing data calls.
michael@0 245 */
michael@0 246 currentDataCalls: null,
michael@0 247
michael@0 248 /**
michael@0 249 * Outgoing messages waiting for SMS-STATUS-REPORT.
michael@0 250 */
michael@0 251 _pendingSentSmsMap: null,
michael@0 252
michael@0 253 /**
michael@0 254 * Index of the RIL_PREFERRED_NETWORK_TYPE_TO_GECKO. Its value should be
michael@0 255 * preserved over rild reset.
michael@0 256 */
michael@0 257 preferredNetworkType: null,
michael@0 258
michael@0 259 /**
michael@0 260 * Marker object.
michael@0 261 */
michael@0 262 pendingNetworkType: null,
michael@0 263
michael@0 264 /**
michael@0 265 * Global Cell Broadcast switch.
michael@0 266 */
michael@0 267 cellBroadcastDisabled: false,
michael@0 268
michael@0 269 /**
michael@0 270 * Global CLIR mode settings.
michael@0 271 */
michael@0 272 clirMode: CLIR_DEFAULT,
michael@0 273
michael@0 274 /**
michael@0 275 * Parsed Cell Broadcast search lists.
michael@0 276 * cellBroadcastConfigs.MMI should be preserved over rild reset.
michael@0 277 */
michael@0 278 cellBroadcastConfigs: null,
michael@0 279 mergedCellBroadcastConfig: null,
michael@0 280
michael@0 281 _receivedSmsCbPagesMap: null,
michael@0 282
michael@0 283 initRILState: function() {
michael@0 284 /**
michael@0 285 * One of the RADIO_STATE_* constants.
michael@0 286 */
michael@0 287 this.radioState = GECKO_RADIOSTATE_UNAVAILABLE;
michael@0 288
michael@0 289 /**
michael@0 290 * True if we are on a CDMA phone.
michael@0 291 */
michael@0 292 this._isCdma = false;
michael@0 293
michael@0 294 /**
michael@0 295 * True if we are in emergency callback mode.
michael@0 296 */
michael@0 297 this._isInEmergencyCbMode = false;
michael@0 298
michael@0 299 /**
michael@0 300 * Set when radio is ready but radio tech is unknown. That is, we are
michael@0 301 * waiting for REQUEST_VOICE_RADIO_TECH
michael@0 302 */
michael@0 303 this._waitingRadioTech = false;
michael@0 304
michael@0 305 /**
michael@0 306 * ICC status. Keeps a reference of the data response to the
michael@0 307 * getICCStatus request.
michael@0 308 */
michael@0 309 this.iccStatus = null;
michael@0 310
michael@0 311 /**
michael@0 312 * Card state
michael@0 313 */
michael@0 314 this.cardState = GECKO_CARDSTATE_UNINITIALIZED;
michael@0 315
michael@0 316 /**
michael@0 317 * Strings
michael@0 318 */
michael@0 319 this.IMEI = null;
michael@0 320 this.IMEISV = null;
michael@0 321 this.ESN = null;
michael@0 322 this.MEID = null;
michael@0 323 this.SMSC = null;
michael@0 324
michael@0 325 /**
michael@0 326 * ICC information that is not exposed to Gaia.
michael@0 327 */
michael@0 328 this.iccInfoPrivate = {};
michael@0 329
michael@0 330 /**
michael@0 331 * ICC information, such as MSISDN, MCC, MNC, SPN...etc.
michael@0 332 */
michael@0 333 this.iccInfo = {};
michael@0 334
michael@0 335 /**
michael@0 336 * CDMA specific information. ex. CDMA Network ID, CDMA System ID... etc.
michael@0 337 */
michael@0 338 this.cdmaHome = null;
michael@0 339
michael@0 340 /**
michael@0 341 * Application identification for apps in ICC.
michael@0 342 */
michael@0 343 this.aid = null;
michael@0 344
michael@0 345 /**
michael@0 346 * Application type for apps in ICC.
michael@0 347 */
michael@0 348 this.appType = null;
michael@0 349
michael@0 350 this.networkSelectionMode = null;
michael@0 351
michael@0 352 this.voiceRegistrationState = {};
michael@0 353 this.dataRegistrationState = {};
michael@0 354
michael@0 355 /**
michael@0 356 * List of strings identifying the network operator.
michael@0 357 */
michael@0 358 this.operator = null;
michael@0 359
michael@0 360 /**
michael@0 361 * String containing the baseband version.
michael@0 362 */
michael@0 363 this.basebandVersion = null;
michael@0 364
michael@0 365 // Clean up this.currentCalls: rild might have restarted.
michael@0 366 for each (let currentCall in this.currentCalls) {
michael@0 367 delete this.currentCalls[currentCall.callIndex];
michael@0 368 this._handleDisconnectedCall(currentCall);
michael@0 369 }
michael@0 370
michael@0 371 // Deactivate this.currentDataCalls: rild might have restarted.
michael@0 372 for each (let datacall in this.currentDataCalls) {
michael@0 373 this.deactivateDataCall(datacall);
michael@0 374 }
michael@0 375
michael@0 376 // Don't clean up this._pendingSentSmsMap
michael@0 377 // because on rild restart: we may continue with the pending segments.
michael@0 378
michael@0 379 /**
michael@0 380 * Whether or not the multiple requests in requestNetworkInfo() are currently
michael@0 381 * being processed
michael@0 382 */
michael@0 383 this._processingNetworkInfo = false;
michael@0 384
michael@0 385 /**
michael@0 386 * Multiple requestNetworkInfo() in a row before finishing the first
michael@0 387 * request, hence we need to fire requestNetworkInfo() again after
michael@0 388 * gathering all necessary stuffs. This is to make sure that ril_worker
michael@0 389 * gets precise network information.
michael@0 390 */
michael@0 391 this._needRepollNetworkInfo = false;
michael@0 392
michael@0 393 /**
michael@0 394 * Pending messages to be send in batch from requestNetworkInfo()
michael@0 395 */
michael@0 396 this._pendingNetworkInfo = {rilMessageType: "networkinfochanged"};
michael@0 397
michael@0 398 /**
michael@0 399 * USSD session flag.
michael@0 400 * Only one USSD session may exist at a time, and the session is assumed
michael@0 401 * to exist until:
michael@0 402 * a) There's a call to cancelUSSD()
michael@0 403 * b) The implementation sends a UNSOLICITED_ON_USSD with a type code
michael@0 404 * of "0" (USSD-Notify/no further action) or "2" (session terminated)
michael@0 405 */
michael@0 406 this._ussdSession = null;
michael@0 407
michael@0 408 /**
michael@0 409 * Regular expresion to parse MMI codes.
michael@0 410 */
michael@0 411 this._mmiRegExp = null;
michael@0 412
michael@0 413 /**
michael@0 414 * Cell Broadcast Search Lists.
michael@0 415 */
michael@0 416 let cbmmi = this.cellBroadcastConfigs && this.cellBroadcastConfigs.MMI;
michael@0 417 this.cellBroadcastConfigs = {
michael@0 418 MMI: cbmmi || null
michael@0 419 };
michael@0 420 this.mergedCellBroadcastConfig = null;
michael@0 421 },
michael@0 422
michael@0 423 /**
michael@0 424 * Parse an integer from a string, falling back to a default value
michael@0 425 * if the the provided value is not a string or does not contain a valid
michael@0 426 * number.
michael@0 427 *
michael@0 428 * @param string
michael@0 429 * String to be parsed.
michael@0 430 * @param defaultValue [optional]
michael@0 431 * Default value to be used.
michael@0 432 * @param radix [optional]
michael@0 433 * A number that represents the numeral system to be used. Default 10.
michael@0 434 */
michael@0 435 parseInt: function(string, defaultValue, radix) {
michael@0 436 let number = parseInt(string, radix || 10);
michael@0 437 if (!isNaN(number)) {
michael@0 438 return number;
michael@0 439 }
michael@0 440 if (defaultValue === undefined) {
michael@0 441 defaultValue = null;
michael@0 442 }
michael@0 443 return defaultValue;
michael@0 444 },
michael@0 445
michael@0 446
michael@0 447 /**
michael@0 448 * Outgoing requests to the RIL. These can be triggered from the
michael@0 449 * main thread via messages that look like this:
michael@0 450 *
michael@0 451 * {rilMessageType: "methodName",
michael@0 452 * extra: "parameters",
michael@0 453 * go: "here"}
michael@0 454 *
michael@0 455 * So if one of the following methods takes arguments, it takes only one,
michael@0 456 * an object, which then contains all of the parameters as attributes.
michael@0 457 * The "@param" documentation is to be interpreted accordingly.
michael@0 458 */
michael@0 459
michael@0 460 /**
michael@0 461 * Retrieve the ICC's status.
michael@0 462 */
michael@0 463 getICCStatus: function() {
michael@0 464 this.context.Buf.simpleRequest(REQUEST_GET_SIM_STATUS);
michael@0 465 },
michael@0 466
michael@0 467 /**
michael@0 468 * Helper function for unlocking ICC locks.
michael@0 469 */
michael@0 470 iccUnlockCardLock: function(options) {
michael@0 471 switch (options.lockType) {
michael@0 472 case GECKO_CARDLOCK_PIN:
michael@0 473 this.enterICCPIN(options);
michael@0 474 break;
michael@0 475 case GECKO_CARDLOCK_PIN2:
michael@0 476 this.enterICCPIN2(options);
michael@0 477 break;
michael@0 478 case GECKO_CARDLOCK_PUK:
michael@0 479 this.enterICCPUK(options);
michael@0 480 break;
michael@0 481 case GECKO_CARDLOCK_PUK2:
michael@0 482 this.enterICCPUK2(options);
michael@0 483 break;
michael@0 484 case GECKO_CARDLOCK_NCK:
michael@0 485 case GECKO_CARDLOCK_NCK1:
michael@0 486 case GECKO_CARDLOCK_NCK2:
michael@0 487 case GECKO_CARDLOCK_HNCK:
michael@0 488 case GECKO_CARDLOCK_CCK:
michael@0 489 case GECKO_CARDLOCK_SPCK:
michael@0 490 case GECKO_CARDLOCK_RCCK: // Fall through.
michael@0 491 case GECKO_CARDLOCK_RSPCK: {
michael@0 492 let type = GECKO_PERSO_LOCK_TO_CARD_PERSO_LOCK[options.lockType];
michael@0 493 this.enterDepersonalization(type, options.pin, options);
michael@0 494 break;
michael@0 495 }
michael@0 496 case GECKO_CARDLOCK_NCK_PUK:
michael@0 497 case GECKO_CARDLOCK_NCK1_PUK:
michael@0 498 case GECKO_CARDLOCK_NCK2_PUK:
michael@0 499 case GECKO_CARDLOCK_HNCK_PUK:
michael@0 500 case GECKO_CARDLOCK_CCK_PUK:
michael@0 501 case GECKO_CARDLOCK_SPCK_PUK:
michael@0 502 case GECKO_CARDLOCK_RCCK_PUK: // Fall through.
michael@0 503 case GECKO_CARDLOCK_RSPCK_PUK: {
michael@0 504 let type = GECKO_PERSO_LOCK_TO_CARD_PERSO_LOCK[options.lockType];
michael@0 505 this.enterDepersonalization(type, options.puk, options);
michael@0 506 break;
michael@0 507 }
michael@0 508 default:
michael@0 509 options.errorMsg = "Unsupported Card Lock.";
michael@0 510 options.success = false;
michael@0 511 this.sendChromeMessage(options);
michael@0 512 }
michael@0 513 },
michael@0 514
michael@0 515 /**
michael@0 516 * Enter a PIN to unlock the ICC.
michael@0 517 *
michael@0 518 * @param pin
michael@0 519 * String containing the PIN.
michael@0 520 * @param [optional] aid
michael@0 521 * AID value.
michael@0 522 */
michael@0 523 enterICCPIN: function(options) {
michael@0 524 let Buf = this.context.Buf;
michael@0 525 Buf.newParcel(REQUEST_ENTER_SIM_PIN, options);
michael@0 526 Buf.writeInt32(this.v5Legacy ? 1 : 2);
michael@0 527 Buf.writeString(options.pin);
michael@0 528 if (!this.v5Legacy) {
michael@0 529 Buf.writeString(options.aid || this.aid);
michael@0 530 }
michael@0 531 Buf.sendParcel();
michael@0 532 },
michael@0 533
michael@0 534 /**
michael@0 535 * Enter a PIN2 to unlock the ICC.
michael@0 536 *
michael@0 537 * @param pin
michael@0 538 * String containing the PIN2.
michael@0 539 * @param [optional] aid
michael@0 540 * AID value.
michael@0 541 */
michael@0 542 enterICCPIN2: function(options) {
michael@0 543 let Buf = this.context.Buf;
michael@0 544 Buf.newParcel(REQUEST_ENTER_SIM_PIN2, options);
michael@0 545 Buf.writeInt32(this.v5Legacy ? 1 : 2);
michael@0 546 Buf.writeString(options.pin);
michael@0 547 if (!this.v5Legacy) {
michael@0 548 Buf.writeString(options.aid || this.aid);
michael@0 549 }
michael@0 550 Buf.sendParcel();
michael@0 551 },
michael@0 552
michael@0 553 /**
michael@0 554 * Requests a network personalization be deactivated.
michael@0 555 *
michael@0 556 * @param type
michael@0 557 * Integer indicating the network personalization be deactivated.
michael@0 558 * @param password
michael@0 559 * String containing the password.
michael@0 560 */
michael@0 561 enterDepersonalization: function(type, password, options) {
michael@0 562 let Buf = this.context.Buf;
michael@0 563 Buf.newParcel(REQUEST_ENTER_NETWORK_DEPERSONALIZATION_CODE, options);
michael@0 564 Buf.writeInt32(type);
michael@0 565 Buf.writeString(password);
michael@0 566 Buf.sendParcel();
michael@0 567 },
michael@0 568
michael@0 569 /**
michael@0 570 * Helper function for changing ICC locks.
michael@0 571 */
michael@0 572 iccSetCardLock: function(options) {
michael@0 573 if (options.newPin !== undefined) { // Change PIN lock.
michael@0 574 switch (options.lockType) {
michael@0 575 case GECKO_CARDLOCK_PIN:
michael@0 576 this.changeICCPIN(options);
michael@0 577 break;
michael@0 578 case GECKO_CARDLOCK_PIN2:
michael@0 579 this.changeICCPIN2(options);
michael@0 580 break;
michael@0 581 default:
michael@0 582 options.errorMsg = "Unsupported Card Lock.";
michael@0 583 options.success = false;
michael@0 584 this.sendChromeMessage(options);
michael@0 585 }
michael@0 586 } else { // Enable/Disable lock.
michael@0 587 switch (options.lockType) {
michael@0 588 case GECKO_CARDLOCK_PIN:
michael@0 589 options.facility = ICC_CB_FACILITY_SIM;
michael@0 590 options.password = options.pin;
michael@0 591 break;
michael@0 592 case GECKO_CARDLOCK_FDN:
michael@0 593 options.facility = ICC_CB_FACILITY_FDN;
michael@0 594 options.password = options.pin2;
michael@0 595 break;
michael@0 596 default:
michael@0 597 options.errorMsg = "Unsupported Card Lock.";
michael@0 598 options.success = false;
michael@0 599 this.sendChromeMessage(options);
michael@0 600 return;
michael@0 601 }
michael@0 602 options.enabled = options.enabled;
michael@0 603 options.serviceClass = ICC_SERVICE_CLASS_VOICE |
michael@0 604 ICC_SERVICE_CLASS_DATA |
michael@0 605 ICC_SERVICE_CLASS_FAX;
michael@0 606 this.setICCFacilityLock(options);
michael@0 607 }
michael@0 608 },
michael@0 609
michael@0 610 /**
michael@0 611 * Change the current ICC PIN number.
michael@0 612 *
michael@0 613 * @param pin
michael@0 614 * String containing the old PIN value
michael@0 615 * @param newPin
michael@0 616 * String containing the new PIN value
michael@0 617 * @param [optional] aid
michael@0 618 * AID value.
michael@0 619 */
michael@0 620 changeICCPIN: function(options) {
michael@0 621 let Buf = this.context.Buf;
michael@0 622 Buf.newParcel(REQUEST_CHANGE_SIM_PIN, options);
michael@0 623 Buf.writeInt32(this.v5Legacy ? 2 : 3);
michael@0 624 Buf.writeString(options.pin);
michael@0 625 Buf.writeString(options.newPin);
michael@0 626 if (!this.v5Legacy) {
michael@0 627 Buf.writeString(options.aid || this.aid);
michael@0 628 }
michael@0 629 Buf.sendParcel();
michael@0 630 },
michael@0 631
michael@0 632 /**
michael@0 633 * Change the current ICC PIN2 number.
michael@0 634 *
michael@0 635 * @param pin
michael@0 636 * String containing the old PIN2 value
michael@0 637 * @param newPin
michael@0 638 * String containing the new PIN2 value
michael@0 639 * @param [optional] aid
michael@0 640 * AID value.
michael@0 641 */
michael@0 642 changeICCPIN2: function(options) {
michael@0 643 let Buf = this.context.Buf;
michael@0 644 Buf.newParcel(REQUEST_CHANGE_SIM_PIN2, options);
michael@0 645 Buf.writeInt32(this.v5Legacy ? 2 : 3);
michael@0 646 Buf.writeString(options.pin);
michael@0 647 Buf.writeString(options.newPin);
michael@0 648 if (!this.v5Legacy) {
michael@0 649 Buf.writeString(options.aid || this.aid);
michael@0 650 }
michael@0 651 Buf.sendParcel();
michael@0 652 },
michael@0 653 /**
michael@0 654 * Supplies ICC PUK and a new PIN to unlock the ICC.
michael@0 655 *
michael@0 656 * @param puk
michael@0 657 * String containing the PUK value.
michael@0 658 * @param newPin
michael@0 659 * String containing the new PIN value.
michael@0 660 * @param [optional] aid
michael@0 661 * AID value.
michael@0 662 */
michael@0 663 enterICCPUK: function(options) {
michael@0 664 let Buf = this.context.Buf;
michael@0 665 Buf.newParcel(REQUEST_ENTER_SIM_PUK, options);
michael@0 666 Buf.writeInt32(this.v5Legacy ? 2 : 3);
michael@0 667 Buf.writeString(options.puk);
michael@0 668 Buf.writeString(options.newPin);
michael@0 669 if (!this.v5Legacy) {
michael@0 670 Buf.writeString(options.aid || this.aid);
michael@0 671 }
michael@0 672 Buf.sendParcel();
michael@0 673 },
michael@0 674
michael@0 675 /**
michael@0 676 * Supplies ICC PUK2 and a new PIN2 to unlock the ICC.
michael@0 677 *
michael@0 678 * @param puk
michael@0 679 * String containing the PUK2 value.
michael@0 680 * @param newPin
michael@0 681 * String containing the new PIN2 value.
michael@0 682 * @param [optional] aid
michael@0 683 * AID value.
michael@0 684 */
michael@0 685 enterICCPUK2: function(options) {
michael@0 686 let Buf = this.context.Buf;
michael@0 687 Buf.newParcel(REQUEST_ENTER_SIM_PUK2, options);
michael@0 688 Buf.writeInt32(this.v5Legacy ? 2 : 3);
michael@0 689 Buf.writeString(options.puk);
michael@0 690 Buf.writeString(options.newPin);
michael@0 691 if (!this.v5Legacy) {
michael@0 692 Buf.writeString(options.aid || this.aid);
michael@0 693 }
michael@0 694 Buf.sendParcel();
michael@0 695 },
michael@0 696
michael@0 697 /**
michael@0 698 * Helper function for fetching the state of ICC locks.
michael@0 699 */
michael@0 700 iccGetCardLockState: function(options) {
michael@0 701 switch (options.lockType) {
michael@0 702 case GECKO_CARDLOCK_PIN:
michael@0 703 options.facility = ICC_CB_FACILITY_SIM;
michael@0 704 break;
michael@0 705 case GECKO_CARDLOCK_FDN:
michael@0 706 options.facility = ICC_CB_FACILITY_FDN;
michael@0 707 break;
michael@0 708 default:
michael@0 709 options.errorMsg = "Unsupported Card Lock.";
michael@0 710 options.success = false;
michael@0 711 this.sendChromeMessage(options);
michael@0 712 return;
michael@0 713 }
michael@0 714
michael@0 715 options.password = ""; // For query no need to provide pin.
michael@0 716 options.serviceClass = ICC_SERVICE_CLASS_VOICE |
michael@0 717 ICC_SERVICE_CLASS_DATA |
michael@0 718 ICC_SERVICE_CLASS_FAX;
michael@0 719 this.queryICCFacilityLock(options);
michael@0 720 },
michael@0 721
michael@0 722 /**
michael@0 723 * Helper function for fetching the number of unlock retries of ICC locks.
michael@0 724 *
michael@0 725 * We only query the retry count when we're on the emulator. The phones do
michael@0 726 * not support the request id and their rild doesn't return an error.
michael@0 727 */
michael@0 728 iccGetCardLockRetryCount: function(options) {
michael@0 729 var selCode = {
michael@0 730 pin: ICC_SEL_CODE_SIM_PIN,
michael@0 731 puk: ICC_SEL_CODE_SIM_PUK,
michael@0 732 pin2: ICC_SEL_CODE_SIM_PIN2,
michael@0 733 puk2: ICC_SEL_CODE_SIM_PUK2,
michael@0 734 nck: ICC_SEL_CODE_PH_NET_PIN,
michael@0 735 cck: ICC_SEL_CODE_PH_CORP_PIN,
michael@0 736 spck: ICC_SEL_CODE_PH_SP_PIN
michael@0 737 };
michael@0 738
michael@0 739 if (typeof(selCode[options.lockType]) === 'undefined') {
michael@0 740 /* unknown lock type */
michael@0 741 options.errorMsg = GECKO_ERROR_GENERIC_FAILURE;
michael@0 742 options.success = false;
michael@0 743 this.sendChromeMessage(options);
michael@0 744 return;
michael@0 745 }
michael@0 746
michael@0 747 if (RILQUIRKS_HAVE_QUERY_ICC_LOCK_RETRY_COUNT) {
michael@0 748 /* Only the emulator supports this request, ... */
michael@0 749 options.selCode = selCode[options.lockType];
michael@0 750 this.queryICCLockRetryCount(options);
michael@0 751 } else {
michael@0 752 /* ... while the phones do not. */
michael@0 753 options.errorMsg = GECKO_ERROR_REQUEST_NOT_SUPPORTED;
michael@0 754 options.success = false;
michael@0 755 this.sendChromeMessage(options);
michael@0 756 }
michael@0 757 },
michael@0 758
michael@0 759 /**
michael@0 760 * Query ICC lock retry count.
michael@0 761 *
michael@0 762 * @param selCode
michael@0 763 * One of ICC_SEL_CODE_*.
michael@0 764 * @param serviceClass
michael@0 765 * One of ICC_SERVICE_CLASS_*.
michael@0 766 */
michael@0 767 queryICCLockRetryCount: function(options) {
michael@0 768 let Buf = this.context.Buf;
michael@0 769 Buf.newParcel(REQUEST_GET_UNLOCK_RETRY_COUNT, options);
michael@0 770 Buf.writeInt32(1);
michael@0 771 Buf.writeString(options.selCode);
michael@0 772 Buf.sendParcel();
michael@0 773 },
michael@0 774
michael@0 775 /**
michael@0 776 * Query ICC facility lock.
michael@0 777 *
michael@0 778 * @param facility
michael@0 779 * One of ICC_CB_FACILITY_*.
michael@0 780 * @param password
michael@0 781 * Password for the facility, or "" if not required.
michael@0 782 * @param serviceClass
michael@0 783 * One of ICC_SERVICE_CLASS_*.
michael@0 784 * @param [optional] aid
michael@0 785 * AID value.
michael@0 786 */
michael@0 787 queryICCFacilityLock: function(options) {
michael@0 788 let Buf = this.context.Buf;
michael@0 789 Buf.newParcel(REQUEST_QUERY_FACILITY_LOCK, options);
michael@0 790 Buf.writeInt32(this.v5Legacy ? 3 : 4);
michael@0 791 Buf.writeString(options.facility);
michael@0 792 Buf.writeString(options.password);
michael@0 793 Buf.writeString(options.serviceClass.toString());
michael@0 794 if (!this.v5Legacy) {
michael@0 795 Buf.writeString(options.aid || this.aid);
michael@0 796 }
michael@0 797 Buf.sendParcel();
michael@0 798 },
michael@0 799
michael@0 800 /**
michael@0 801 * Set ICC facility lock.
michael@0 802 *
michael@0 803 * @param facility
michael@0 804 * One of ICC_CB_FACILITY_*.
michael@0 805 * @param enabled
michael@0 806 * true to enable, false to disable.
michael@0 807 * @param password
michael@0 808 * Password for the facility, or "" if not required.
michael@0 809 * @param serviceClass
michael@0 810 * One of ICC_SERVICE_CLASS_*.
michael@0 811 * @param [optional] aid
michael@0 812 * AID value.
michael@0 813 */
michael@0 814 setICCFacilityLock: function(options) {
michael@0 815 let Buf = this.context.Buf;
michael@0 816 Buf.newParcel(REQUEST_SET_FACILITY_LOCK, options);
michael@0 817 Buf.writeInt32(this.v5Legacy ? 4 : 5);
michael@0 818 Buf.writeString(options.facility);
michael@0 819 Buf.writeString(options.enabled ? "1" : "0");
michael@0 820 Buf.writeString(options.password);
michael@0 821 Buf.writeString(options.serviceClass.toString());
michael@0 822 if (!this.v5Legacy) {
michael@0 823 Buf.writeString(options.aid || this.aid);
michael@0 824 }
michael@0 825 Buf.sendParcel();
michael@0 826 },
michael@0 827
michael@0 828 /**
michael@0 829 * Request an ICC I/O operation.
michael@0 830 *
michael@0 831 * See TS 27.007 "restricted SIM" operation, "AT Command +CRSM".
michael@0 832 * The sequence is in the same order as how libril reads this parcel,
michael@0 833 * see the struct RIL_SIM_IO_v5 or RIL_SIM_IO_v6 defined in ril.h
michael@0 834 *
michael@0 835 * @param command
michael@0 836 * The I/O command, one of the ICC_COMMAND_* constants.
michael@0 837 * @param fileId
michael@0 838 * The file to operate on, one of the ICC_EF_* constants.
michael@0 839 * @param pathId
michael@0 840 * String type, check the 'pathid' parameter from TS 27.007 +CRSM.
michael@0 841 * @param p1, p2, p3
michael@0 842 * Arbitrary integer parameters for the command.
michael@0 843 * @param [optional] dataWriter
michael@0 844 * The function for writing string parameter for the ICC_COMMAND_UPDATE_RECORD.
michael@0 845 * @param [optional] pin2
michael@0 846 * String containing the PIN2.
michael@0 847 * @param [optional] aid
michael@0 848 * AID value.
michael@0 849 */
michael@0 850 iccIO: function(options) {
michael@0 851 let Buf = this.context.Buf;
michael@0 852 Buf.newParcel(REQUEST_SIM_IO, options);
michael@0 853 Buf.writeInt32(options.command);
michael@0 854 Buf.writeInt32(options.fileId);
michael@0 855 Buf.writeString(options.pathId);
michael@0 856 Buf.writeInt32(options.p1);
michael@0 857 Buf.writeInt32(options.p2);
michael@0 858 Buf.writeInt32(options.p3);
michael@0 859
michael@0 860 // Write data.
michael@0 861 if (options.command == ICC_COMMAND_UPDATE_RECORD &&
michael@0 862 options.dataWriter) {
michael@0 863 options.dataWriter(options.p3);
michael@0 864 } else {
michael@0 865 Buf.writeString(null);
michael@0 866 }
michael@0 867
michael@0 868 // Write pin2.
michael@0 869 if (options.command == ICC_COMMAND_UPDATE_RECORD &&
michael@0 870 options.pin2) {
michael@0 871 Buf.writeString(options.pin2);
michael@0 872 } else {
michael@0 873 Buf.writeString(null);
michael@0 874 }
michael@0 875
michael@0 876 if (!this.v5Legacy) {
michael@0 877 Buf.writeString(options.aid || this.aid);
michael@0 878 }
michael@0 879 Buf.sendParcel();
michael@0 880 },
michael@0 881
michael@0 882 /**
michael@0 883 * Get IMSI.
michael@0 884 *
michael@0 885 * @param [optional] aid
michael@0 886 * AID value.
michael@0 887 */
michael@0 888 getIMSI: function(aid) {
michael@0 889 let Buf = this.context.Buf;
michael@0 890 if (this.v5Legacy) {
michael@0 891 Buf.simpleRequest(REQUEST_GET_IMSI);
michael@0 892 return;
michael@0 893 }
michael@0 894 Buf.newParcel(REQUEST_GET_IMSI);
michael@0 895 Buf.writeInt32(1);
michael@0 896 Buf.writeString(aid || this.aid);
michael@0 897 Buf.sendParcel();
michael@0 898 },
michael@0 899
michael@0 900 /**
michael@0 901 * Read UICC Phonebook contacts.
michael@0 902 *
michael@0 903 * @param contactType
michael@0 904 * "adn" or "fdn".
michael@0 905 * @param requestId
michael@0 906 * Request id from RadioInterfaceLayer.
michael@0 907 */
michael@0 908 readICCContacts: function(options) {
michael@0 909 if (!this.appType) {
michael@0 910 options.errorMsg = CONTACT_ERR_REQUEST_NOT_SUPPORTED;
michael@0 911 this.sendChromeMessage(options);
michael@0 912 return;
michael@0 913 }
michael@0 914
michael@0 915 this.context.ICCContactHelper.readICCContacts(
michael@0 916 this.appType,
michael@0 917 options.contactType,
michael@0 918 function onsuccess(contacts) {
michael@0 919 for (let i = 0; i < contacts.length; i++) {
michael@0 920 let contact = contacts[i];
michael@0 921 let pbrIndex = contact.pbrIndex || 0;
michael@0 922 let recordIndex = pbrIndex * ICC_MAX_LINEAR_FIXED_RECORDS + contact.recordId;
michael@0 923 contact.contactId = this.iccInfo.iccid + recordIndex;
michael@0 924 }
michael@0 925 // Reuse 'options' to get 'requestId' and 'contactType'.
michael@0 926 options.contacts = contacts;
michael@0 927 this.sendChromeMessage(options);
michael@0 928 }.bind(this),
michael@0 929 function onerror(errorMsg) {
michael@0 930 options.errorMsg = errorMsg;
michael@0 931 this.sendChromeMessage(options);
michael@0 932 }.bind(this));
michael@0 933 },
michael@0 934
michael@0 935 /**
michael@0 936 * Update UICC Phonebook.
michael@0 937 *
michael@0 938 * @param contactType "adn" or "fdn".
michael@0 939 * @param contact The contact will be updated.
michael@0 940 * @param pin2 PIN2 is required for updating FDN.
michael@0 941 * @param requestId Request id from RadioInterfaceLayer.
michael@0 942 */
michael@0 943 updateICCContact: function(options) {
michael@0 944 let onsuccess = function onsuccess() {
michael@0 945 let recordIndex =
michael@0 946 contact.pbrIndex * ICC_MAX_LINEAR_FIXED_RECORDS + contact.recordId;
michael@0 947 contact.contactId = this.iccInfo.iccid + recordIndex;
michael@0 948 // Reuse 'options' to get 'requestId' and 'contactType'.
michael@0 949 this.sendChromeMessage(options);
michael@0 950 }.bind(this);
michael@0 951
michael@0 952 let onerror = function onerror(errorMsg) {
michael@0 953 options.errorMsg = errorMsg;
michael@0 954 this.sendChromeMessage(options);
michael@0 955 }.bind(this);
michael@0 956
michael@0 957 if (!this.appType || !options.contact) {
michael@0 958 onerror(CONTACT_ERR_REQUEST_NOT_SUPPORTED );
michael@0 959 return;
michael@0 960 }
michael@0 961
michael@0 962 let contact = options.contact;
michael@0 963 let iccid = this.iccInfo.iccid;
michael@0 964 let isValidRecordId = false;
michael@0 965 if (typeof contact.contactId === "string" &&
michael@0 966 contact.contactId.startsWith(iccid)) {
michael@0 967 let recordIndex = contact.contactId.substring(iccid.length);
michael@0 968 contact.pbrIndex = Math.floor(recordIndex / ICC_MAX_LINEAR_FIXED_RECORDS);
michael@0 969 contact.recordId = recordIndex % ICC_MAX_LINEAR_FIXED_RECORDS;
michael@0 970 isValidRecordId = contact.recordId > 0 && contact.recordId < 0xff;
michael@0 971 }
michael@0 972
michael@0 973 if (DEBUG) {
michael@0 974 this.context.debug("Update ICC Contact " + JSON.stringify(contact));
michael@0 975 }
michael@0 976
michael@0 977 let ICCContactHelper = this.context.ICCContactHelper;
michael@0 978 // If contact has 'recordId' property, updates corresponding record.
michael@0 979 // If not, inserts the contact into a free record.
michael@0 980 if (isValidRecordId) {
michael@0 981 ICCContactHelper.updateICCContact(
michael@0 982 this.appType, options.contactType, contact, options.pin2, onsuccess, onerror);
michael@0 983 } else {
michael@0 984 ICCContactHelper.addICCContact(
michael@0 985 this.appType, options.contactType, contact, options.pin2, onsuccess, onerror);
michael@0 986 }
michael@0 987 },
michael@0 988
michael@0 989 /**
michael@0 990 * Request the phone's radio to be enabled or disabled.
michael@0 991 *
michael@0 992 * @param enabled
michael@0 993 * Boolean indicating the desired state.
michael@0 994 */
michael@0 995 setRadioEnabled: function(options) {
michael@0 996 let Buf = this.context.Buf;
michael@0 997 Buf.newParcel(REQUEST_RADIO_POWER, options);
michael@0 998 Buf.writeInt32(1);
michael@0 999 Buf.writeInt32(options.enabled ? 1 : 0);
michael@0 1000 Buf.sendParcel();
michael@0 1001 },
michael@0 1002
michael@0 1003 /**
michael@0 1004 * Query call waiting status via MMI.
michael@0 1005 */
michael@0 1006 _handleQueryMMICallWaiting: function(options) {
michael@0 1007 let Buf = this.context.Buf;
michael@0 1008
michael@0 1009 function callback(options) {
michael@0 1010 options.length = Buf.readInt32();
michael@0 1011 options.enabled = (Buf.readInt32() === 1);
michael@0 1012 let services = Buf.readInt32();
michael@0 1013 if (options.enabled) {
michael@0 1014 options.statusMessage = MMI_SM_KS_SERVICE_ENABLED_FOR;
michael@0 1015 let serviceClass = [];
michael@0 1016 for (let serviceClassMask = 1;
michael@0 1017 serviceClassMask <= ICC_SERVICE_CLASS_MAX;
michael@0 1018 serviceClassMask <<= 1) {
michael@0 1019 if ((serviceClassMask & services) !== 0) {
michael@0 1020 serviceClass.push(MMI_KS_SERVICE_CLASS_MAPPING[serviceClassMask]);
michael@0 1021 }
michael@0 1022 }
michael@0 1023 options.additionalInformation = serviceClass;
michael@0 1024 } else {
michael@0 1025 options.statusMessage = MMI_SM_KS_SERVICE_DISABLED;
michael@0 1026 }
michael@0 1027
michael@0 1028 // Prevent DataCloneError when sending chrome messages.
michael@0 1029 delete options.callback;
michael@0 1030 this.sendChromeMessage(options);
michael@0 1031 }
michael@0 1032
michael@0 1033 options.callback = callback;
michael@0 1034 this.queryCallWaiting(options);
michael@0 1035 },
michael@0 1036
michael@0 1037 /**
michael@0 1038 * Set call waiting status via MMI.
michael@0 1039 */
michael@0 1040 _handleSetMMICallWaiting: function(options) {
michael@0 1041 function callback(options) {
michael@0 1042 if (options.enabled) {
michael@0 1043 options.statusMessage = MMI_SM_KS_SERVICE_ENABLED;
michael@0 1044 } else {
michael@0 1045 options.statusMessage = MMI_SM_KS_SERVICE_DISABLED;
michael@0 1046 }
michael@0 1047
michael@0 1048 // Prevent DataCloneError when sending chrome messages.
michael@0 1049 delete options.callback;
michael@0 1050 this.sendChromeMessage(options);
michael@0 1051 }
michael@0 1052
michael@0 1053 options.callback = callback;
michael@0 1054 this.setCallWaiting(options);
michael@0 1055 },
michael@0 1056
michael@0 1057 /**
michael@0 1058 * Query call waiting status.
michael@0 1059 *
michael@0 1060 */
michael@0 1061 queryCallWaiting: function(options) {
michael@0 1062 let Buf = this.context.Buf;
michael@0 1063 Buf.newParcel(REQUEST_QUERY_CALL_WAITING, options);
michael@0 1064 Buf.writeInt32(1);
michael@0 1065 // As per 3GPP TS 24.083, section 1.6 UE doesn't need to send service
michael@0 1066 // class parameter in call waiting interrogation to network
michael@0 1067 Buf.writeInt32(ICC_SERVICE_CLASS_NONE);
michael@0 1068 Buf.sendParcel();
michael@0 1069 },
michael@0 1070
michael@0 1071 /**
michael@0 1072 * Set call waiting status.
michael@0 1073 *
michael@0 1074 * @param on
michael@0 1075 * Boolean indicating the desired waiting status.
michael@0 1076 */
michael@0 1077 setCallWaiting: function(options) {
michael@0 1078 let Buf = this.context.Buf;
michael@0 1079 Buf.newParcel(REQUEST_SET_CALL_WAITING, options);
michael@0 1080 Buf.writeInt32(2);
michael@0 1081 Buf.writeInt32(options.enabled ? 1 : 0);
michael@0 1082 Buf.writeInt32(options.serviceClass !== undefined ?
michael@0 1083 options.serviceClass : ICC_SERVICE_CLASS_VOICE);
michael@0 1084 Buf.sendParcel();
michael@0 1085 },
michael@0 1086
michael@0 1087 /**
michael@0 1088 * Queries current CLIP status.
michael@0 1089 *
michael@0 1090 * (MMI request for code "*#30#")
michael@0 1091 *
michael@0 1092 */
michael@0 1093 queryCLIP: function(options) {
michael@0 1094 this.context.Buf.simpleRequest(REQUEST_QUERY_CLIP, options);
michael@0 1095 },
michael@0 1096
michael@0 1097 /**
michael@0 1098 * Queries current CLIR status.
michael@0 1099 *
michael@0 1100 */
michael@0 1101 getCLIR: function(options) {
michael@0 1102 this.context.Buf.simpleRequest(REQUEST_GET_CLIR, options);
michael@0 1103 },
michael@0 1104
michael@0 1105 /**
michael@0 1106 * Enables or disables the presentation of the calling line identity (CLI) to
michael@0 1107 * the called party when originating a call.
michael@0 1108 *
michael@0 1109 * @param options.clirMode
michael@0 1110 * Is one of the CLIR_* constants in
michael@0 1111 * nsIDOMMozMobileConnection interface.
michael@0 1112 */
michael@0 1113 setCLIR: function(options) {
michael@0 1114 if (options) {
michael@0 1115 this.clirMode = options.clirMode;
michael@0 1116 }
michael@0 1117 let Buf = this.context.Buf;
michael@0 1118 Buf.newParcel(REQUEST_SET_CLIR, options);
michael@0 1119 Buf.writeInt32(1);
michael@0 1120 Buf.writeInt32(this.clirMode);
michael@0 1121 Buf.sendParcel();
michael@0 1122 },
michael@0 1123
michael@0 1124 /**
michael@0 1125 * Set screen state.
michael@0 1126 *
michael@0 1127 * @param on
michael@0 1128 * Boolean indicating whether the screen should be on or off.
michael@0 1129 */
michael@0 1130 setScreenState: function(options) {
michael@0 1131 let Buf = this.context.Buf;
michael@0 1132 Buf.newParcel(REQUEST_SCREEN_STATE);
michael@0 1133 Buf.writeInt32(1);
michael@0 1134 Buf.writeInt32(options.on ? 1 : 0);
michael@0 1135 Buf.sendParcel();
michael@0 1136 },
michael@0 1137
michael@0 1138 getVoiceRegistrationState: function() {
michael@0 1139 this.context.Buf.simpleRequest(REQUEST_VOICE_REGISTRATION_STATE);
michael@0 1140 },
michael@0 1141
michael@0 1142 getVoiceRadioTechnology: function() {
michael@0 1143 this.context.Buf.simpleRequest(REQUEST_VOICE_RADIO_TECH);
michael@0 1144 },
michael@0 1145
michael@0 1146 getDataRegistrationState: function() {
michael@0 1147 this.context.Buf.simpleRequest(REQUEST_DATA_REGISTRATION_STATE);
michael@0 1148 },
michael@0 1149
michael@0 1150 getOperator: function() {
michael@0 1151 this.context.Buf.simpleRequest(REQUEST_OPERATOR);
michael@0 1152 },
michael@0 1153
michael@0 1154 /**
michael@0 1155 * Set the preferred network type.
michael@0 1156 *
michael@0 1157 * @param options An object contains a valid index of
michael@0 1158 * RIL_PREFERRED_NETWORK_TYPE_TO_GECKO as its `networkType`
michael@0 1159 * attribute, or undefined to set current preferred network
michael@0 1160 * type.
michael@0 1161 */
michael@0 1162 setPreferredNetworkType: function(options) {
michael@0 1163 if (options) {
michael@0 1164 this.preferredNetworkType = options.networkType;
michael@0 1165 }
michael@0 1166 if (this.preferredNetworkType == null) {
michael@0 1167 return;
michael@0 1168 }
michael@0 1169
michael@0 1170 let Buf = this.context.Buf;
michael@0 1171 Buf.newParcel(REQUEST_SET_PREFERRED_NETWORK_TYPE, options);
michael@0 1172 Buf.writeInt32(1);
michael@0 1173 Buf.writeInt32(this.preferredNetworkType);
michael@0 1174 Buf.sendParcel();
michael@0 1175 },
michael@0 1176
michael@0 1177 /**
michael@0 1178 * Get the preferred network type.
michael@0 1179 */
michael@0 1180 getPreferredNetworkType: function(options) {
michael@0 1181 this.context.Buf.simpleRequest(REQUEST_GET_PREFERRED_NETWORK_TYPE, options);
michael@0 1182 },
michael@0 1183
michael@0 1184 /**
michael@0 1185 * Request various states about the network.
michael@0 1186 */
michael@0 1187 requestNetworkInfo: function() {
michael@0 1188 if (this._processingNetworkInfo) {
michael@0 1189 if (DEBUG) {
michael@0 1190 this.context.debug("Network info requested, but we're already " +
michael@0 1191 "requesting network info.");
michael@0 1192 }
michael@0 1193 this._needRepollNetworkInfo = true;
michael@0 1194 return;
michael@0 1195 }
michael@0 1196
michael@0 1197 if (DEBUG) this.context.debug("Requesting network info");
michael@0 1198
michael@0 1199 this._processingNetworkInfo = true;
michael@0 1200 this.getVoiceRegistrationState();
michael@0 1201 this.getDataRegistrationState(); //TODO only GSM
michael@0 1202 this.getOperator();
michael@0 1203 this.getNetworkSelectionMode();
michael@0 1204 this.getSignalStrength();
michael@0 1205 },
michael@0 1206
michael@0 1207 /**
michael@0 1208 * Get the available networks
michael@0 1209 */
michael@0 1210 getAvailableNetworks: function(options) {
michael@0 1211 if (DEBUG) this.context.debug("Getting available networks");
michael@0 1212 let Buf = this.context.Buf;
michael@0 1213 Buf.newParcel(REQUEST_QUERY_AVAILABLE_NETWORKS, options);
michael@0 1214 Buf.sendParcel();
michael@0 1215 },
michael@0 1216
michael@0 1217 /**
michael@0 1218 * Request the radio's network selection mode
michael@0 1219 */
michael@0 1220 getNetworkSelectionMode: function() {
michael@0 1221 if (DEBUG) this.context.debug("Getting network selection mode");
michael@0 1222 this.context.Buf.simpleRequest(REQUEST_QUERY_NETWORK_SELECTION_MODE);
michael@0 1223 },
michael@0 1224
michael@0 1225 /**
michael@0 1226 * Tell the radio to automatically choose a voice/data network
michael@0 1227 */
michael@0 1228 selectNetworkAuto: function(options) {
michael@0 1229 if (DEBUG) this.context.debug("Setting automatic network selection");
michael@0 1230 this.context.Buf.simpleRequest(REQUEST_SET_NETWORK_SELECTION_AUTOMATIC, options);
michael@0 1231 },
michael@0 1232
michael@0 1233 /**
michael@0 1234 * Set the roaming preference mode
michael@0 1235 */
michael@0 1236 setRoamingPreference: function(options) {
michael@0 1237 let roamingMode = CDMA_ROAMING_PREFERENCE_TO_GECKO.indexOf(options.mode);
michael@0 1238
michael@0 1239 if (roamingMode === -1) {
michael@0 1240 options.errorMsg = GECKO_ERROR_INVALID_PARAMETER;
michael@0 1241 this.sendChromeMessage(options);
michael@0 1242 return;
michael@0 1243 }
michael@0 1244
michael@0 1245 let Buf = this.context.Buf;
michael@0 1246 Buf.newParcel(REQUEST_CDMA_SET_ROAMING_PREFERENCE, options);
michael@0 1247 Buf.writeInt32(1);
michael@0 1248 Buf.writeInt32(roamingMode);
michael@0 1249 Buf.sendParcel();
michael@0 1250 },
michael@0 1251
michael@0 1252 /**
michael@0 1253 * Get the roaming preference mode
michael@0 1254 */
michael@0 1255 queryRoamingPreference: function(options) {
michael@0 1256 this.context.Buf.simpleRequest(REQUEST_CDMA_QUERY_ROAMING_PREFERENCE, options);
michael@0 1257 },
michael@0 1258
michael@0 1259 /**
michael@0 1260 * Set the voice privacy mode
michael@0 1261 */
michael@0 1262 setVoicePrivacyMode: function(options) {
michael@0 1263 let Buf = this.context.Buf;
michael@0 1264 Buf.newParcel(REQUEST_CDMA_SET_PREFERRED_VOICE_PRIVACY_MODE, options);
michael@0 1265 Buf.writeInt32(1);
michael@0 1266 Buf.writeInt32(options.enabled ? 1 : 0);
michael@0 1267 Buf.sendParcel();
michael@0 1268 },
michael@0 1269
michael@0 1270 /**
michael@0 1271 * Get the voice privacy mode
michael@0 1272 */
michael@0 1273 queryVoicePrivacyMode: function(options) {
michael@0 1274 this.context.Buf.simpleRequest(REQUEST_CDMA_QUERY_PREFERRED_VOICE_PRIVACY_MODE, options);
michael@0 1275 },
michael@0 1276
michael@0 1277 /**
michael@0 1278 * Open Logical UICC channel (aid) for Secure Element access
michael@0 1279 */
michael@0 1280 iccOpenChannel: function(options) {
michael@0 1281 if (DEBUG) {
michael@0 1282 this.context.debug("iccOpenChannel: " + JSON.stringify(options));
michael@0 1283 }
michael@0 1284
michael@0 1285 let Buf = this.context.Buf;
michael@0 1286 Buf.newParcel(REQUEST_SIM_OPEN_CHANNEL, options);
michael@0 1287 Buf.writeString(options.aid);
michael@0 1288 Buf.sendParcel();
michael@0 1289 },
michael@0 1290
michael@0 1291 /**
michael@0 1292 * Exchange APDU data on an open Logical UICC channel
michael@0 1293 */
michael@0 1294 iccExchangeAPDU: function(options) {
michael@0 1295 if (DEBUG) this.context.debug("iccExchangeAPDU: " + JSON.stringify(options));
michael@0 1296
michael@0 1297 let cla = options.apdu.cla;
michael@0 1298 let command = options.apdu.command;
michael@0 1299 let channel = options.channel;
michael@0 1300 let path = options.apdu.path || "";
michael@0 1301 let data = options.apdu.data || "";
michael@0 1302 let data2 = options.apdu.data2 || "";
michael@0 1303
michael@0 1304 let p1 = options.apdu.p1;
michael@0 1305 let p2 = options.apdu.p2;
michael@0 1306 let p3 = options.apdu.p3; // Extra
michael@0 1307
michael@0 1308 let Buf = this.context.Buf;
michael@0 1309 Buf.newParcel(REQUEST_SIM_ACCESS_CHANNEL, options);
michael@0 1310 Buf.writeInt32(cla);
michael@0 1311 Buf.writeInt32(command);
michael@0 1312 Buf.writeInt32(channel);
michael@0 1313 Buf.writeString(path); // path
michael@0 1314 Buf.writeInt32(p1);
michael@0 1315 Buf.writeInt32(p2);
michael@0 1316 Buf.writeInt32(p3);
michael@0 1317 Buf.writeString(data); // generic data field.
michael@0 1318 Buf.writeString(data2);
michael@0 1319
michael@0 1320 Buf.sendParcel();
michael@0 1321 },
michael@0 1322
michael@0 1323 /**
michael@0 1324 * Close Logical UICC channel
michael@0 1325 */
michael@0 1326 iccCloseChannel: function(options) {
michael@0 1327 if (DEBUG) this.context.debug("iccCloseChannel: " + JSON.stringify(options));
michael@0 1328
michael@0 1329 let Buf = this.context.Buf;
michael@0 1330 Buf.newParcel(REQUEST_SIM_CLOSE_CHANNEL, options);
michael@0 1331 Buf.writeInt32(1);
michael@0 1332 Buf.writeInt32(options.channel);
michael@0 1333 Buf.sendParcel();
michael@0 1334 },
michael@0 1335
michael@0 1336 /**
michael@0 1337 * Tell the radio to choose a specific voice/data network
michael@0 1338 */
michael@0 1339 selectNetwork: function(options) {
michael@0 1340 if (DEBUG) {
michael@0 1341 this.context.debug("Setting manual network selection: " +
michael@0 1342 options.mcc + ", " + options.mnc);
michael@0 1343 }
michael@0 1344
michael@0 1345 let numeric = (options.mcc && options.mnc) ? options.mcc + options.mnc : null;
michael@0 1346 let Buf = this.context.Buf;
michael@0 1347 Buf.newParcel(REQUEST_SET_NETWORK_SELECTION_MANUAL, options);
michael@0 1348 Buf.writeString(numeric);
michael@0 1349 Buf.sendParcel();
michael@0 1350 },
michael@0 1351
michael@0 1352 /**
michael@0 1353 * Get current calls.
michael@0 1354 */
michael@0 1355 getCurrentCalls: function() {
michael@0 1356 this.context.Buf.simpleRequest(REQUEST_GET_CURRENT_CALLS);
michael@0 1357 },
michael@0 1358
michael@0 1359 /**
michael@0 1360 * Get the signal strength.
michael@0 1361 */
michael@0 1362 getSignalStrength: function() {
michael@0 1363 this.context.Buf.simpleRequest(REQUEST_SIGNAL_STRENGTH);
michael@0 1364 },
michael@0 1365
michael@0 1366 getIMEI: function(options) {
michael@0 1367 this.context.Buf.simpleRequest(REQUEST_GET_IMEI, options);
michael@0 1368 },
michael@0 1369
michael@0 1370 getIMEISV: function() {
michael@0 1371 this.context.Buf.simpleRequest(REQUEST_GET_IMEISV);
michael@0 1372 },
michael@0 1373
michael@0 1374 getDeviceIdentity: function() {
michael@0 1375 this.context.Buf.simpleRequest(REQUEST_DEVICE_IDENTITY);
michael@0 1376 },
michael@0 1377
michael@0 1378 getBasebandVersion: function() {
michael@0 1379 this.context.Buf.simpleRequest(REQUEST_BASEBAND_VERSION);
michael@0 1380 },
michael@0 1381
michael@0 1382 sendExitEmergencyCbModeRequest: function(options) {
michael@0 1383 this.context.Buf.simpleRequest(REQUEST_EXIT_EMERGENCY_CALLBACK_MODE, options);
michael@0 1384 },
michael@0 1385
michael@0 1386 getCdmaSubscription: function() {
michael@0 1387 this.context.Buf.simpleRequest(REQUEST_CDMA_SUBSCRIPTION);
michael@0 1388 },
michael@0 1389
michael@0 1390 exitEmergencyCbMode: function(options) {
michael@0 1391 // The function could be called by an API from RadioInterfaceLayer or by
michael@0 1392 // ril_worker itself. From ril_worker, we won't pass the parameter
michael@0 1393 // 'options'. In this case, it is marked as internal.
michael@0 1394 if (!options) {
michael@0 1395 options = {internal: true};
michael@0 1396 }
michael@0 1397 this._cancelEmergencyCbModeTimeout();
michael@0 1398 this.sendExitEmergencyCbModeRequest(options);
michael@0 1399 },
michael@0 1400
michael@0 1401 /**
michael@0 1402 * Cache the request for making an emergency call when radio is off. The
michael@0 1403 * request shall include two types of callback functions. 'callback' is
michael@0 1404 * called when radio is ready, and 'onerror' is called when turning radio
michael@0 1405 * on fails.
michael@0 1406 */
michael@0 1407 cachedDialRequest : null,
michael@0 1408
michael@0 1409 /**
michael@0 1410 * Dial the phone.
michael@0 1411 *
michael@0 1412 * @param number
michael@0 1413 * String containing the number to dial.
michael@0 1414 * @param clirMode
michael@0 1415 * Integer for showing/hidding the caller Id to the called party.
michael@0 1416 * @param uusInfo
michael@0 1417 * Integer doing something XXX TODO
michael@0 1418 */
michael@0 1419 dial: function(options) {
michael@0 1420 let onerror = (function onerror(options, errorMsg) {
michael@0 1421 options.success = false;
michael@0 1422 options.errorMsg = errorMsg;
michael@0 1423 this.sendChromeMessage(options);
michael@0 1424 }).bind(this, options);
michael@0 1425
michael@0 1426 if (this._isEmergencyNumber(options.number)) {
michael@0 1427 this.dialEmergencyNumber(options, onerror);
michael@0 1428 } else {
michael@0 1429 if (!this._isCdma) {
michael@0 1430 // TODO: Both dial() and sendMMI() functions should be unified at some
michael@0 1431 // point in the future. In the mean time we handle temporary CLIR MMI
michael@0 1432 // commands through the dial() function. Please see bug 889737.
michael@0 1433 let mmi = this._parseMMI(options.number);
michael@0 1434 if (mmi && this._isTemporaryModeCLIR(mmi)) {
michael@0 1435 options.number = mmi.dialNumber;
michael@0 1436 // In temporary mode, MMI_PROCEDURE_ACTIVATION means allowing CLI
michael@0 1437 // presentation, i.e. CLIR_SUPPRESSION. See TS 22.030, Annex B.
michael@0 1438 options.clirMode = mmi.procedure == MMI_PROCEDURE_ACTIVATION ?
michael@0 1439 CLIR_SUPPRESSION : CLIR_INVOCATION;
michael@0 1440 }
michael@0 1441 }
michael@0 1442 this.dialNonEmergencyNumber(options, onerror);
michael@0 1443 }
michael@0 1444 },
michael@0 1445
michael@0 1446 dialNonEmergencyNumber: function(options, onerror) {
michael@0 1447 if (this.radioState == GECKO_RADIOSTATE_OFF) {
michael@0 1448 // Notify error in establishing the call without radio.
michael@0 1449 onerror(GECKO_ERROR_RADIO_NOT_AVAILABLE);
michael@0 1450 return;
michael@0 1451 }
michael@0 1452
michael@0 1453 if (this.voiceRegistrationState.emergencyCallsOnly ||
michael@0 1454 options.isDialEmergency) {
michael@0 1455 onerror(RIL_CALL_FAILCAUSE_TO_GECKO_CALL_ERROR[CALL_FAIL_UNOBTAINABLE_NUMBER]);
michael@0 1456 return;
michael@0 1457 }
michael@0 1458
michael@0 1459 // Exit emergency callback mode when user dial a non-emergency call.
michael@0 1460 if (this._isInEmergencyCbMode) {
michael@0 1461 this.exitEmergencyCbMode();
michael@0 1462 }
michael@0 1463
michael@0 1464 if (this._isCdma && Object.keys(this.currentCalls).length == 1) {
michael@0 1465 // Make a Cdma 3way call.
michael@0 1466 options.featureStr = options.number;
michael@0 1467 this.sendCdmaFlashCommand(options);
michael@0 1468 } else {
michael@0 1469 options.request = REQUEST_DIAL;
michael@0 1470 this.sendDialRequest(options);
michael@0 1471 }
michael@0 1472 },
michael@0 1473
michael@0 1474 dialEmergencyNumber: function(options, onerror) {
michael@0 1475 options.request = RILQUIRKS_REQUEST_USE_DIAL_EMERGENCY_CALL ?
michael@0 1476 REQUEST_DIAL_EMERGENCY_CALL : REQUEST_DIAL;
michael@0 1477 if (this.radioState == GECKO_RADIOSTATE_OFF) {
michael@0 1478 if (DEBUG) {
michael@0 1479 this.context.debug("Automatically enable radio for an emergency call.");
michael@0 1480 }
michael@0 1481
michael@0 1482 if (!this.cachedDialRequest) {
michael@0 1483 this.cachedDialRequest = {};
michael@0 1484 }
michael@0 1485 this.cachedDialRequest.onerror = onerror;
michael@0 1486 this.cachedDialRequest.callback = this.sendDialRequest.bind(this, options);
michael@0 1487 this.setRadioEnabled({enabled: true});
michael@0 1488 return;
michael@0 1489 }
michael@0 1490
michael@0 1491 if (this._isCdma && Object.keys(this.currentCalls).length == 1) {
michael@0 1492 // Make a Cdma 3way call.
michael@0 1493 options.featureStr = options.number;
michael@0 1494 this.sendCdmaFlashCommand(options);
michael@0 1495 } else {
michael@0 1496 this.sendDialRequest(options);
michael@0 1497 }
michael@0 1498 },
michael@0 1499
michael@0 1500 sendDialRequest: function(options) {
michael@0 1501 // Always succeed.
michael@0 1502 options.success = true;
michael@0 1503 this.sendChromeMessage(options);
michael@0 1504 this._createPendingOutgoingCall(options);
michael@0 1505
michael@0 1506 let Buf = this.context.Buf;
michael@0 1507 Buf.newParcel(options.request, options);
michael@0 1508 Buf.writeString(options.number);
michael@0 1509 Buf.writeInt32(options.clirMode || 0);
michael@0 1510 Buf.writeInt32(options.uusInfo || 0);
michael@0 1511 // TODO Why do we need this extra 0? It was put it in to make this
michael@0 1512 // match the format of the binary message.
michael@0 1513 Buf.writeInt32(0);
michael@0 1514 Buf.sendParcel();
michael@0 1515 },
michael@0 1516
michael@0 1517 sendCdmaFlashCommand: function(options) {
michael@0 1518 let Buf = this.context.Buf;
michael@0 1519 options.isCdma = true;
michael@0 1520 options.request = REQUEST_CDMA_FLASH;
michael@0 1521 Buf.newParcel(options.request, options);
michael@0 1522 Buf.writeString(options.featureStr);
michael@0 1523 Buf.sendParcel();
michael@0 1524 },
michael@0 1525
michael@0 1526 /**
michael@0 1527 * Hang up all calls
michael@0 1528 */
michael@0 1529 hangUpAll: function() {
michael@0 1530 for (let callIndex in this.currentCalls) {
michael@0 1531 this.hangUp({callIndex: callIndex});
michael@0 1532 }
michael@0 1533 },
michael@0 1534
michael@0 1535 /**
michael@0 1536 * Hang up the phone.
michael@0 1537 *
michael@0 1538 * @param callIndex
michael@0 1539 * Call index (1-based) as reported by REQUEST_GET_CURRENT_CALLS.
michael@0 1540 */
michael@0 1541 hangUp: function(options) {
michael@0 1542 let call = this.currentCalls[options.callIndex];
michael@0 1543 if (!call) {
michael@0 1544 return;
michael@0 1545 }
michael@0 1546
michael@0 1547 let callIndex = call.callIndex;
michael@0 1548 if (callIndex === OUTGOING_PLACEHOLDER_CALL_INDEX) {
michael@0 1549 if (DEBUG) this.context.debug("Hang up pending outgoing call.");
michael@0 1550 this._removeVoiceCall(call, GECKO_CALL_ERROR_NORMAL_CALL_CLEARING);
michael@0 1551 return;
michael@0 1552 }
michael@0 1553
michael@0 1554 call.hangUpLocal = true;
michael@0 1555
michael@0 1556 if (call.state === CALL_STATE_HOLDING) {
michael@0 1557 this.sendHangUpBackgroundRequest(callIndex);
michael@0 1558 } else {
michael@0 1559 this.sendHangUpRequest(callIndex);
michael@0 1560 }
michael@0 1561 },
michael@0 1562
michael@0 1563 sendHangUpRequest: function(callIndex) {
michael@0 1564 let Buf = this.context.Buf;
michael@0 1565 Buf.newParcel(REQUEST_HANGUP);
michael@0 1566 Buf.writeInt32(1);
michael@0 1567 Buf.writeInt32(callIndex);
michael@0 1568 Buf.sendParcel();
michael@0 1569 },
michael@0 1570
michael@0 1571 sendHangUpBackgroundRequest: function(callIndex) {
michael@0 1572 let Buf = this.context.Buf;
michael@0 1573 Buf.simpleRequest(REQUEST_HANGUP_WAITING_OR_BACKGROUND);
michael@0 1574 },
michael@0 1575
michael@0 1576 /**
michael@0 1577 * Mute or unmute the radio.
michael@0 1578 *
michael@0 1579 * @param mute
michael@0 1580 * Boolean to indicate whether to mute or unmute the radio.
michael@0 1581 */
michael@0 1582 setMute: function(options) {
michael@0 1583 let Buf = this.context.Buf;
michael@0 1584 Buf.newParcel(REQUEST_SET_MUTE);
michael@0 1585 Buf.writeInt32(1);
michael@0 1586 Buf.writeInt32(options.muted ? 1 : 0);
michael@0 1587 Buf.sendParcel();
michael@0 1588 },
michael@0 1589
michael@0 1590 /**
michael@0 1591 * Answer an incoming/waiting call.
michael@0 1592 *
michael@0 1593 * @param callIndex
michael@0 1594 * Call index of the call to answer.
michael@0 1595 */
michael@0 1596 answerCall: function(options) {
michael@0 1597 // Check for races. Since we dispatched the incoming/waiting call
michael@0 1598 // notification the incoming/waiting call may have changed. The main
michael@0 1599 // thread thinks that it is answering the call with the given index,
michael@0 1600 // so only answer if that is still incoming/waiting.
michael@0 1601 let call = this.currentCalls[options.callIndex];
michael@0 1602 if (!call) {
michael@0 1603 return;
michael@0 1604 }
michael@0 1605
michael@0 1606 let Buf = this.context.Buf;
michael@0 1607 switch (call.state) {
michael@0 1608 case CALL_STATE_INCOMING:
michael@0 1609 Buf.simpleRequest(REQUEST_ANSWER);
michael@0 1610 break;
michael@0 1611 case CALL_STATE_WAITING:
michael@0 1612 // Answer the waiting (second) call, and hold the first call.
michael@0 1613 Buf.simpleRequest(REQUEST_SWITCH_WAITING_OR_HOLDING_AND_ACTIVE);
michael@0 1614 break;
michael@0 1615 }
michael@0 1616 },
michael@0 1617
michael@0 1618 /**
michael@0 1619 * Reject an incoming/waiting call.
michael@0 1620 *
michael@0 1621 * @param callIndex
michael@0 1622 * Call index of the call to reject.
michael@0 1623 */
michael@0 1624 rejectCall: function(options) {
michael@0 1625 // Check for races. Since we dispatched the incoming/waiting call
michael@0 1626 // notification the incoming/waiting call may have changed. The main
michael@0 1627 // thread thinks that it is rejecting the call with the given index,
michael@0 1628 // so only reject if that is still incoming/waiting.
michael@0 1629 let call = this.currentCalls[options.callIndex];
michael@0 1630 if (!call) {
michael@0 1631 return;
michael@0 1632 }
michael@0 1633
michael@0 1634 call.hangUpLocal = true;
michael@0 1635
michael@0 1636 let Buf = this.context.Buf;
michael@0 1637 if (this._isCdma) {
michael@0 1638 // AT+CHLD=0 means "release held or UDUB."
michael@0 1639 Buf.simpleRequest(REQUEST_HANGUP_WAITING_OR_BACKGROUND);
michael@0 1640 return;
michael@0 1641 }
michael@0 1642
michael@0 1643 switch (call.state) {
michael@0 1644 case CALL_STATE_INCOMING:
michael@0 1645 Buf.simpleRequest(REQUEST_UDUB);
michael@0 1646 break;
michael@0 1647 case CALL_STATE_WAITING:
michael@0 1648 // Reject the waiting (second) call, and remain the first call.
michael@0 1649 Buf.simpleRequest(REQUEST_HANGUP_WAITING_OR_BACKGROUND);
michael@0 1650 break;
michael@0 1651 }
michael@0 1652 },
michael@0 1653
michael@0 1654 holdCall: function(options) {
michael@0 1655 let call = this.currentCalls[options.callIndex];
michael@0 1656 if (!call) {
michael@0 1657 options.errorMsg = GECKO_ERROR_GENERIC_FAILURE;
michael@0 1658 options.success = false;
michael@0 1659 this.sendChromeMessage(options);
michael@0 1660 return;
michael@0 1661 }
michael@0 1662
michael@0 1663 let Buf = this.context.Buf;
michael@0 1664 if (this._isCdma) {
michael@0 1665 options.featureStr = "";
michael@0 1666 this.sendCdmaFlashCommand(options);
michael@0 1667 } else if (call.state == CALL_STATE_ACTIVE) {
michael@0 1668 Buf.simpleRequest(REQUEST_SWITCH_WAITING_OR_HOLDING_AND_ACTIVE, options);
michael@0 1669 }
michael@0 1670 },
michael@0 1671
michael@0 1672 resumeCall: function(options) {
michael@0 1673 let call = this.currentCalls[options.callIndex];
michael@0 1674 if (!call) {
michael@0 1675 options.errorMsg = GECKO_ERROR_GENERIC_FAILURE;
michael@0 1676 options.success = false;
michael@0 1677 this.sendChromeMessage(options);
michael@0 1678 return;
michael@0 1679 }
michael@0 1680
michael@0 1681 let Buf = this.context.Buf;
michael@0 1682 if (this._isCdma) {
michael@0 1683 options.featureStr = "";
michael@0 1684 this.sendCdmaFlashCommand(options);
michael@0 1685 } else if (call.state == CALL_STATE_HOLDING) {
michael@0 1686 Buf.simpleRequest(REQUEST_SWITCH_WAITING_OR_HOLDING_AND_ACTIVE, options);
michael@0 1687 }
michael@0 1688 },
michael@0 1689
michael@0 1690 // Flag indicating whether user has requested making a conference call.
michael@0 1691 _hasConferenceRequest: false,
michael@0 1692
michael@0 1693 conferenceCall: function(options) {
michael@0 1694 let Buf = this.context.Buf;
michael@0 1695 if (this._isCdma) {
michael@0 1696 options.featureStr = "";
michael@0 1697 this.sendCdmaFlashCommand(options);
michael@0 1698 } else {
michael@0 1699 this._hasConferenceRequest = true;
michael@0 1700 Buf.simpleRequest(REQUEST_CONFERENCE, options);
michael@0 1701 }
michael@0 1702 },
michael@0 1703
michael@0 1704 separateCall: function(options) {
michael@0 1705 let call = this.currentCalls[options.callIndex];
michael@0 1706 if (!call) {
michael@0 1707 options.errorName = "removeError";
michael@0 1708 options.errorMsg = GECKO_ERROR_GENERIC_FAILURE;
michael@0 1709 options.success = false;
michael@0 1710 this.sendChromeMessage(options);
michael@0 1711 return;
michael@0 1712 }
michael@0 1713
michael@0 1714 let Buf = this.context.Buf;
michael@0 1715 if (this._isCdma) {
michael@0 1716 options.featureStr = "";
michael@0 1717 this.sendCdmaFlashCommand(options);
michael@0 1718 } else {
michael@0 1719 Buf.newParcel(REQUEST_SEPARATE_CONNECTION, options);
michael@0 1720 Buf.writeInt32(1);
michael@0 1721 Buf.writeInt32(options.callIndex);
michael@0 1722 Buf.sendParcel();
michael@0 1723 }
michael@0 1724 },
michael@0 1725
michael@0 1726 holdConference: function() {
michael@0 1727 if (this._isCdma) {
michael@0 1728 return;
michael@0 1729 }
michael@0 1730
michael@0 1731 this.context.Buf.simpleRequest(REQUEST_SWITCH_WAITING_OR_HOLDING_AND_ACTIVE);
michael@0 1732 },
michael@0 1733
michael@0 1734 resumeConference: function() {
michael@0 1735 if (this._isCdma) {
michael@0 1736 return;
michael@0 1737 }
michael@0 1738
michael@0 1739 this.context.Buf.simpleRequest(REQUEST_SWITCH_WAITING_OR_HOLDING_AND_ACTIVE);
michael@0 1740 },
michael@0 1741
michael@0 1742 /**
michael@0 1743 * Send an SMS.
michael@0 1744 *
michael@0 1745 * The `options` parameter object should contain the following attributes:
michael@0 1746 *
michael@0 1747 * @param number
michael@0 1748 * String containing the recipient number.
michael@0 1749 * @param body
michael@0 1750 * String containing the message text.
michael@0 1751 * @param envelopeId
michael@0 1752 * Numeric value identifying the sms request.
michael@0 1753 */
michael@0 1754 sendSMS: function(options) {
michael@0 1755 options.langIndex = options.langIndex || PDU_NL_IDENTIFIER_DEFAULT;
michael@0 1756 options.langShiftIndex = options.langShiftIndex || PDU_NL_IDENTIFIER_DEFAULT;
michael@0 1757
michael@0 1758 if (!options.retryCount) {
michael@0 1759 options.retryCount = 0;
michael@0 1760 }
michael@0 1761
michael@0 1762 if (!options.segmentSeq) {
michael@0 1763 // Fist segment to send
michael@0 1764 options.segmentSeq = 1;
michael@0 1765 options.body = options.segments[0].body;
michael@0 1766 options.encodedBodyLength = options.segments[0].encodedBodyLength;
michael@0 1767 }
michael@0 1768
michael@0 1769 let Buf = this.context.Buf;
michael@0 1770 if (this._isCdma) {
michael@0 1771 Buf.newParcel(REQUEST_CDMA_SEND_SMS, options);
michael@0 1772 this.context.CdmaPDUHelper.writeMessage(options);
michael@0 1773 } else {
michael@0 1774 Buf.newParcel(REQUEST_SEND_SMS, options);
michael@0 1775 Buf.writeInt32(2);
michael@0 1776 Buf.writeString(options.SMSC);
michael@0 1777 this.context.GsmPDUHelper.writeMessage(options);
michael@0 1778 }
michael@0 1779 Buf.sendParcel();
michael@0 1780 },
michael@0 1781
michael@0 1782 /**
michael@0 1783 * Acknowledge the receipt and handling of an SMS.
michael@0 1784 *
michael@0 1785 * @param success
michael@0 1786 * Boolean indicating whether the message was successfuly handled.
michael@0 1787 * @param cause
michael@0 1788 * SMS_* constant indicating the reason for unsuccessful handling.
michael@0 1789 */
michael@0 1790 acknowledgeGsmSms: function(success, cause) {
michael@0 1791 let Buf = this.context.Buf;
michael@0 1792 Buf.newParcel(REQUEST_SMS_ACKNOWLEDGE);
michael@0 1793 Buf.writeInt32(2);
michael@0 1794 Buf.writeInt32(success ? 1 : 0);
michael@0 1795 Buf.writeInt32(cause);
michael@0 1796 Buf.sendParcel();
michael@0 1797 },
michael@0 1798
michael@0 1799 /**
michael@0 1800 * Acknowledge the receipt and handling of an SMS.
michael@0 1801 *
michael@0 1802 * @param success
michael@0 1803 * Boolean indicating whether the message was successfuly handled.
michael@0 1804 */
michael@0 1805 ackSMS: function(options) {
michael@0 1806 if (options.result == PDU_FCS_RESERVED) {
michael@0 1807 return;
michael@0 1808 }
michael@0 1809 if (this._isCdma) {
michael@0 1810 this.acknowledgeCdmaSms(options.result == PDU_FCS_OK, options.result);
michael@0 1811 } else {
michael@0 1812 this.acknowledgeGsmSms(options.result == PDU_FCS_OK, options.result);
michael@0 1813 }
michael@0 1814 },
michael@0 1815
michael@0 1816 /**
michael@0 1817 * Acknowledge the receipt and handling of a CDMA SMS.
michael@0 1818 *
michael@0 1819 * @param success
michael@0 1820 * Boolean indicating whether the message was successfuly handled.
michael@0 1821 * @param cause
michael@0 1822 * SMS_* constant indicating the reason for unsuccessful handling.
michael@0 1823 */
michael@0 1824 acknowledgeCdmaSms: function(success, cause) {
michael@0 1825 let Buf = this.context.Buf;
michael@0 1826 Buf.newParcel(REQUEST_CDMA_SMS_ACKNOWLEDGE);
michael@0 1827 Buf.writeInt32(success ? 0 : 1);
michael@0 1828 Buf.writeInt32(cause);
michael@0 1829 Buf.sendParcel();
michael@0 1830 },
michael@0 1831
michael@0 1832 /**
michael@0 1833 * Update received MWI into EF_MWIS.
michael@0 1834 */
michael@0 1835 updateMwis: function(options) {
michael@0 1836 if (this.context.ICCUtilsHelper.isICCServiceAvailable("MWIS")) {
michael@0 1837 this.context.SimRecordHelper.updateMWIS(options.mwi);
michael@0 1838 }
michael@0 1839 },
michael@0 1840
michael@0 1841 setCellBroadcastDisabled: function(options) {
michael@0 1842 this.cellBroadcastDisabled = options.disabled;
michael@0 1843
michael@0 1844 // If |this.mergedCellBroadcastConfig| is null, either we haven't finished
michael@0 1845 // reading required SIM files, or no any channel is ever configured. In
michael@0 1846 // the former case, we'll call |this.updateCellBroadcastConfig()| later
michael@0 1847 // with correct configs; in the latter case, we don't bother resetting CB
michael@0 1848 // to disabled again.
michael@0 1849 if (this.mergedCellBroadcastConfig) {
michael@0 1850 this.updateCellBroadcastConfig();
michael@0 1851 }
michael@0 1852 },
michael@0 1853
michael@0 1854 setCellBroadcastSearchList: function(options) {
michael@0 1855 let getSearchListStr = function(aSearchList) {
michael@0 1856 if (typeof aSearchList === "string" || aSearchList instanceof String) {
michael@0 1857 return aSearchList;
michael@0 1858 }
michael@0 1859
michael@0 1860 // TODO: Set search list for CDMA/GSM individually. Bug 990926
michael@0 1861 let prop = this._isCdma ? "cdma" : "gsm";
michael@0 1862
michael@0 1863 return aSearchList && aSearchList[prop];
michael@0 1864 }.bind(this);
michael@0 1865
michael@0 1866 try {
michael@0 1867 let str = getSearchListStr(options.searchList);
michael@0 1868 this.cellBroadcastConfigs.MMI = this._convertCellBroadcastSearchList(str);
michael@0 1869 options.success = true;
michael@0 1870 } catch (e) {
michael@0 1871 if (DEBUG) {
michael@0 1872 this.context.debug("Invalid Cell Broadcast search list: " + e);
michael@0 1873 }
michael@0 1874 options.success = false;
michael@0 1875 }
michael@0 1876
michael@0 1877 this.sendChromeMessage(options);
michael@0 1878 if (!options.success) {
michael@0 1879 return;
michael@0 1880 }
michael@0 1881
michael@0 1882 this._mergeAllCellBroadcastConfigs();
michael@0 1883 },
michael@0 1884
michael@0 1885 updateCellBroadcastConfig: function() {
michael@0 1886 let activate = !this.cellBroadcastDisabled &&
michael@0 1887 (this.mergedCellBroadcastConfig != null) &&
michael@0 1888 (this.mergedCellBroadcastConfig.length > 0);
michael@0 1889 if (activate) {
michael@0 1890 this.setSmsBroadcastConfig(this.mergedCellBroadcastConfig);
michael@0 1891 } else {
michael@0 1892 // It's unnecessary to set config first if we're deactivating.
michael@0 1893 this.setSmsBroadcastActivation(false);
michael@0 1894 }
michael@0 1895 },
michael@0 1896
michael@0 1897 setGsmSmsBroadcastConfig: function(config) {
michael@0 1898 let Buf = this.context.Buf;
michael@0 1899 Buf.newParcel(REQUEST_GSM_SET_BROADCAST_SMS_CONFIG);
michael@0 1900
michael@0 1901 let numConfigs = config ? config.length / 2 : 0;
michael@0 1902 Buf.writeInt32(numConfigs);
michael@0 1903 for (let i = 0; i < config.length;) {
michael@0 1904 Buf.writeInt32(config[i++]);
michael@0 1905 Buf.writeInt32(config[i++]);
michael@0 1906 Buf.writeInt32(0x00);
michael@0 1907 Buf.writeInt32(0xFF);
michael@0 1908 Buf.writeInt32(1);
michael@0 1909 }
michael@0 1910
michael@0 1911 Buf.sendParcel();
michael@0 1912 },
michael@0 1913
michael@0 1914 /**
michael@0 1915 * Send CDMA SMS broadcast config.
michael@0 1916 *
michael@0 1917 * @see 3GPP2 C.R1001 Sec. 9.2 and 9.3
michael@0 1918 */
michael@0 1919 setCdmaSmsBroadcastConfig: function(config) {
michael@0 1920 let Buf = this.context.Buf;
michael@0 1921 // |config| is an array of half-closed range: [[from, to), [from, to), ...].
michael@0 1922 // It will be further decomposed, ex: [1, 4) => 1, 2, 3.
michael@0 1923 Buf.newParcel(REQUEST_CDMA_SET_BROADCAST_SMS_CONFIG);
michael@0 1924
michael@0 1925 let numConfigs = 0;
michael@0 1926 for (let i = 0; i < config.length; i += 2) {
michael@0 1927 numConfigs += (config[i+1] - config[i]);
michael@0 1928 }
michael@0 1929
michael@0 1930 Buf.writeInt32(numConfigs);
michael@0 1931 for (let i = 0; i < config.length;) {
michael@0 1932 let begin = config[i++];
michael@0 1933 let end = config[i++];
michael@0 1934
michael@0 1935 for (let j = begin; j < end; ++j) {
michael@0 1936 Buf.writeInt32(j);
michael@0 1937 Buf.writeInt32(0); // Language Indicator: Unknown or unspecified.
michael@0 1938 Buf.writeInt32(1);
michael@0 1939 }
michael@0 1940 }
michael@0 1941
michael@0 1942 Buf.sendParcel();
michael@0 1943 },
michael@0 1944
michael@0 1945 setSmsBroadcastConfig: function(config) {
michael@0 1946 if (this._isCdma) {
michael@0 1947 this.setCdmaSmsBroadcastConfig(config);
michael@0 1948 } else {
michael@0 1949 this.setGsmSmsBroadcastConfig(config);
michael@0 1950 }
michael@0 1951 },
michael@0 1952
michael@0 1953 setSmsBroadcastActivation: function(activate) {
michael@0 1954 let parcelType = this._isCdma ? REQUEST_CDMA_SMS_BROADCAST_ACTIVATION :
michael@0 1955 REQUEST_GSM_SMS_BROADCAST_ACTIVATION;
michael@0 1956 let Buf = this.context.Buf;
michael@0 1957 Buf.newParcel(parcelType);
michael@0 1958 Buf.writeInt32(1);
michael@0 1959 // See hardware/ril/include/telephony/ril.h, 0 - Activate, 1 - Turn off.
michael@0 1960 Buf.writeInt32(activate ? 0 : 1);
michael@0 1961 Buf.sendParcel();
michael@0 1962 },
michael@0 1963
michael@0 1964 /**
michael@0 1965 * Start a DTMF Tone.
michael@0 1966 *
michael@0 1967 * @param dtmfChar
michael@0 1968 * DTMF signal to send, 0-9, *, +
michael@0 1969 */
michael@0 1970 startTone: function(options) {
michael@0 1971 let Buf = this.context.Buf;
michael@0 1972 Buf.newParcel(REQUEST_DTMF_START);
michael@0 1973 Buf.writeString(options.dtmfChar);
michael@0 1974 Buf.sendParcel();
michael@0 1975 },
michael@0 1976
michael@0 1977 stopTone: function() {
michael@0 1978 this.context.Buf.simpleRequest(REQUEST_DTMF_STOP);
michael@0 1979 },
michael@0 1980
michael@0 1981 /**
michael@0 1982 * Send a DTMF tone.
michael@0 1983 *
michael@0 1984 * @param dtmfChar
michael@0 1985 * DTMF signal to send, 0-9, *, +
michael@0 1986 */
michael@0 1987 sendTone: function(options) {
michael@0 1988 let Buf = this.context.Buf;
michael@0 1989 Buf.newParcel(REQUEST_DTMF);
michael@0 1990 Buf.writeString(options.dtmfChar);
michael@0 1991 Buf.sendParcel();
michael@0 1992 },
michael@0 1993
michael@0 1994 /**
michael@0 1995 * Get the Short Message Service Center address.
michael@0 1996 */
michael@0 1997 getSmscAddress: function(options) {
michael@0 1998 if (!this.SMSC) {
michael@0 1999 this.context.Buf.simpleRequest(REQUEST_GET_SMSC_ADDRESS, options);
michael@0 2000 return;
michael@0 2001 }
michael@0 2002
michael@0 2003 if (!options || options.rilMessageType !== "getSmscAddress") {
michael@0 2004 return;
michael@0 2005 }
michael@0 2006
michael@0 2007 options.smscAddress = this.SMSC;
michael@0 2008 options.errorMsg = RIL_ERROR_TO_GECKO_ERROR[options.rilRequestError];
michael@0 2009 this.sendChromeMessage(options);
michael@0 2010 },
michael@0 2011
michael@0 2012 /**
michael@0 2013 * Set the Short Message Service Center address.
michael@0 2014 *
michael@0 2015 * @param smscAddress
michael@0 2016 * Short Message Service Center address in PDU format.
michael@0 2017 */
michael@0 2018 setSmscAddress: function(options) {
michael@0 2019 let Buf = this.context.Buf;
michael@0 2020 Buf.newParcel(REQUEST_SET_SMSC_ADDRESS, options);
michael@0 2021 Buf.writeString(options.smscAddress);
michael@0 2022 Buf.sendParcel();
michael@0 2023 },
michael@0 2024
michael@0 2025 /**
michael@0 2026 * Setup a data call.
michael@0 2027 *
michael@0 2028 * @param radioTech
michael@0 2029 * Integer to indicate radio technology.
michael@0 2030 * DATACALL_RADIOTECHNOLOGY_CDMA => CDMA.
michael@0 2031 * DATACALL_RADIOTECHNOLOGY_GSM => GSM.
michael@0 2032 * @param apn
michael@0 2033 * String containing the name of the APN to connect to.
michael@0 2034 * @param user
michael@0 2035 * String containing the username for the APN.
michael@0 2036 * @param passwd
michael@0 2037 * String containing the password for the APN.
michael@0 2038 * @param chappap
michael@0 2039 * Integer containing CHAP/PAP auth type.
michael@0 2040 * DATACALL_AUTH_NONE => PAP and CHAP is never performed.
michael@0 2041 * DATACALL_AUTH_PAP => PAP may be performed.
michael@0 2042 * DATACALL_AUTH_CHAP => CHAP may be performed.
michael@0 2043 * DATACALL_AUTH_PAP_OR_CHAP => PAP / CHAP may be performed.
michael@0 2044 * @param pdptype
michael@0 2045 * String containing PDP type to request. ("IP", "IPV6", ...)
michael@0 2046 */
michael@0 2047 setupDataCall: function(options) {
michael@0 2048 // From ./hardware/ril/include/telephony/ril.h:
michael@0 2049 // ((const char **)data)[0] Radio technology to use: 0-CDMA, 1-GSM/UMTS, 2...
michael@0 2050 // for values above 2 this is RIL_RadioTechnology + 2.
michael@0 2051 //
michael@0 2052 // From frameworks/base/telephony/java/com/android/internal/telephony/DataConnection.java:
michael@0 2053 // if the mRilVersion < 6, radio technology must be GSM/UMTS or CDMA.
michael@0 2054 // Otherwise, it must be + 2
michael@0 2055 //
michael@0 2056 // See also bug 901232 and 867873
michael@0 2057 let radioTech;
michael@0 2058 if (this.v5Legacy) {
michael@0 2059 radioTech = this._isCdma ? DATACALL_RADIOTECHNOLOGY_CDMA
michael@0 2060 : DATACALL_RADIOTECHNOLOGY_GSM;
michael@0 2061 } else {
michael@0 2062 radioTech = options.radioTech + 2;
michael@0 2063 }
michael@0 2064 let Buf = this.context.Buf;
michael@0 2065 let token = Buf.newParcel(REQUEST_SETUP_DATA_CALL, options);
michael@0 2066 Buf.writeInt32(7);
michael@0 2067 Buf.writeString(radioTech.toString());
michael@0 2068 Buf.writeString(DATACALL_PROFILE_DEFAULT.toString());
michael@0 2069 Buf.writeString(options.apn);
michael@0 2070 Buf.writeString(options.user);
michael@0 2071 Buf.writeString(options.passwd);
michael@0 2072 Buf.writeString(options.chappap.toString());
michael@0 2073 Buf.writeString(options.pdptype);
michael@0 2074 Buf.sendParcel();
michael@0 2075 return token;
michael@0 2076 },
michael@0 2077
michael@0 2078 /**
michael@0 2079 * Deactivate a data call.
michael@0 2080 *
michael@0 2081 * @param cid
michael@0 2082 * String containing CID.
michael@0 2083 * @param reason
michael@0 2084 * One of DATACALL_DEACTIVATE_* constants.
michael@0 2085 */
michael@0 2086 deactivateDataCall: function(options) {
michael@0 2087 let datacall = this.currentDataCalls[options.cid];
michael@0 2088 if (!datacall) {
michael@0 2089 return;
michael@0 2090 }
michael@0 2091
michael@0 2092 let Buf = this.context.Buf;
michael@0 2093 Buf.newParcel(REQUEST_DEACTIVATE_DATA_CALL, options);
michael@0 2094 Buf.writeInt32(2);
michael@0 2095 Buf.writeString(options.cid);
michael@0 2096 Buf.writeString(options.reason || DATACALL_DEACTIVATE_NO_REASON);
michael@0 2097 Buf.sendParcel();
michael@0 2098
michael@0 2099 datacall.state = GECKO_NETWORK_STATE_DISCONNECTING;
michael@0 2100 this.sendChromeMessage(datacall);
michael@0 2101 },
michael@0 2102
michael@0 2103 /**
michael@0 2104 * Get a list of data calls.
michael@0 2105 */
michael@0 2106 getDataCallList: function() {
michael@0 2107 this.context.Buf.simpleRequest(REQUEST_DATA_CALL_LIST);
michael@0 2108 },
michael@0 2109
michael@0 2110 _attachDataRegistration: false,
michael@0 2111 /**
michael@0 2112 * Manually attach/detach data registration.
michael@0 2113 *
michael@0 2114 * @param attach
michael@0 2115 * Boolean value indicating attach or detach.
michael@0 2116 */
michael@0 2117 setDataRegistration: function(options) {
michael@0 2118 let request = options.attach ? RIL_REQUEST_GPRS_ATTACH :
michael@0 2119 RIL_REQUEST_GPRS_DETACH;
michael@0 2120 this._attachDataRegistration = options.attach;
michael@0 2121 this.context.Buf.simpleRequest(request);
michael@0 2122 },
michael@0 2123
michael@0 2124 /**
michael@0 2125 * Get failure casue code for the most recently failed PDP context.
michael@0 2126 */
michael@0 2127 getFailCauseCode: function(callback) {
michael@0 2128 this.context.Buf.simpleRequest(REQUEST_LAST_CALL_FAIL_CAUSE,
michael@0 2129 {callback: callback});
michael@0 2130 },
michael@0 2131
michael@0 2132 /**
michael@0 2133 * Helper to parse MMI/USSD string. TS.22.030 Figure 3.5.3.2.
michael@0 2134 */
michael@0 2135 _parseMMI: function(mmiString) {
michael@0 2136 if (!mmiString || !mmiString.length) {
michael@0 2137 return null;
michael@0 2138 }
michael@0 2139
michael@0 2140 let matches = this._matchMMIRegexp(mmiString);
michael@0 2141 if (matches) {
michael@0 2142 // After successfully executing the regular expresion over the MMI string,
michael@0 2143 // the following match groups should contain:
michael@0 2144 // 1 = full MMI string that might be used as a USSD request.
michael@0 2145 // 2 = MMI procedure.
michael@0 2146 // 3 = Service code.
michael@0 2147 // 5 = SIA.
michael@0 2148 // 7 = SIB.
michael@0 2149 // 9 = SIC.
michael@0 2150 // 11 = Password registration.
michael@0 2151 // 12 = Dialing number.
michael@0 2152 return {
michael@0 2153 fullMMI: matches[MMI_MATCH_GROUP_FULL_MMI],
michael@0 2154 procedure: matches[MMI_MATCH_GROUP_MMI_PROCEDURE],
michael@0 2155 serviceCode: matches[MMI_MATCH_GROUP_SERVICE_CODE],
michael@0 2156 sia: matches[MMI_MATCH_GROUP_SIA],
michael@0 2157 sib: matches[MMI_MATCH_GROUP_SIB],
michael@0 2158 sic: matches[MMI_MATCH_GROUP_SIC],
michael@0 2159 pwd: matches[MMI_MATCH_GROUP_PWD_CONFIRM],
michael@0 2160 dialNumber: matches[MMI_MATCH_GROUP_DIALING_NUMBER]
michael@0 2161 };
michael@0 2162 }
michael@0 2163
michael@0 2164 if (this._isPoundString(mmiString) ||
michael@0 2165 this._isMMIShortString(mmiString)) {
michael@0 2166 return {
michael@0 2167 fullMMI: mmiString
michael@0 2168 };
michael@0 2169 }
michael@0 2170
michael@0 2171 return null;
michael@0 2172 },
michael@0 2173
michael@0 2174 /**
michael@0 2175 * Helper to parse MMI string via regular expression. TS.22.030 Figure
michael@0 2176 * 3.5.3.2.
michael@0 2177 */
michael@0 2178 _matchMMIRegexp: function(mmiString) {
michael@0 2179 // Regexp to parse and process the MMI code.
michael@0 2180 if (this._mmiRegExp == null) {
michael@0 2181 // The first group of the regexp takes the whole MMI string.
michael@0 2182 // The second group takes the MMI procedure that can be:
michael@0 2183 // - Activation (*SC*SI#).
michael@0 2184 // - Deactivation (#SC*SI#).
michael@0 2185 // - Interrogation (*#SC*SI#).
michael@0 2186 // - Registration (**SC*SI#).
michael@0 2187 // - Erasure (##SC*SI#).
michael@0 2188 // where SC = Service Code (2 or 3 digits) and SI = Supplementary Info
michael@0 2189 // (variable length).
michael@0 2190 let pattern = "((\\*[*#]?|##?)";
michael@0 2191
michael@0 2192 // Third group of the regexp looks for the MMI Service code, which is a
michael@0 2193 // 2 or 3 digits that uniquely specifies the Supplementary Service
michael@0 2194 // associated with the MMI code.
michael@0 2195 pattern += "(\\d{2,3})";
michael@0 2196
michael@0 2197 // Groups from 4 to 9 looks for the MMI Supplementary Information SIA,
michael@0 2198 // SIB and SIC. SIA may comprise e.g. a PIN code or Directory Number,
michael@0 2199 // SIB may be used to specify the tele or bearer service and SIC to
michael@0 2200 // specify the value of the "No Reply Condition Timer". Where a particular
michael@0 2201 // service request does not require any SI, "*SI" is not entered. The use
michael@0 2202 // of SIA, SIB and SIC is optional and shall be entered in any of the
michael@0 2203 // following formats:
michael@0 2204 // - *SIA*SIB*SIC#
michael@0 2205 // - *SIA*SIB#
michael@0 2206 // - *SIA**SIC#
michael@0 2207 // - *SIA#
michael@0 2208 // - **SIB*SIC#
michael@0 2209 // - ***SISC#
michael@0 2210 pattern += "(\\*([^*#]*)(\\*([^*#]*)(\\*([^*#]*)";
michael@0 2211
michael@0 2212 // The eleventh group takes the password for the case of a password
michael@0 2213 // registration procedure.
michael@0 2214 pattern += "(\\*([^*#]*))?)?)?)?#)";
michael@0 2215
michael@0 2216 // The last group takes the dial string after the #.
michael@0 2217 pattern += "([^#]*)";
michael@0 2218
michael@0 2219 this._mmiRegExp = new RegExp(pattern);
michael@0 2220 }
michael@0 2221
michael@0 2222 // Regex only applys for those well-defined MMI strings (refer to TS.22.030
michael@0 2223 // Annex B), otherwise, null should be the expected return value.
michael@0 2224 return this._mmiRegExp.exec(mmiString);
michael@0 2225 },
michael@0 2226
michael@0 2227 /**
michael@0 2228 * Helper to parse # string. TS.22.030 Figure 3.5.3.2.
michael@0 2229 */
michael@0 2230 _isPoundString: function(mmiString) {
michael@0 2231 return (mmiString.charAt(mmiString.length - 1) === MMI_END_OF_USSD);
michael@0 2232 },
michael@0 2233
michael@0 2234 /**
michael@0 2235 * Helper to parse short string. TS.22.030 Figure 3.5.3.2.
michael@0 2236 */
michael@0 2237 _isMMIShortString: function(mmiString) {
michael@0 2238 if (mmiString.length > 2) {
michael@0 2239 return false;
michael@0 2240 }
michael@0 2241
michael@0 2242 if (this._isEmergencyNumber(mmiString)) {
michael@0 2243 return false;
michael@0 2244 }
michael@0 2245
michael@0 2246 // In a call case.
michael@0 2247 if (Object.getOwnPropertyNames(this.currentCalls).length > 0) {
michael@0 2248 return true;
michael@0 2249 }
michael@0 2250
michael@0 2251 if ((mmiString.length != 2) || (mmiString.charAt(0) !== '1')) {
michael@0 2252 return true;
michael@0 2253 }
michael@0 2254
michael@0 2255 return false;
michael@0 2256 },
michael@0 2257
michael@0 2258 sendMMI: function(options) {
michael@0 2259 if (DEBUG) {
michael@0 2260 this.context.debug("SendMMI " + JSON.stringify(options));
michael@0 2261 }
michael@0 2262 let mmiString = options.mmi;
michael@0 2263 let mmi = this._parseMMI(mmiString);
michael@0 2264
michael@0 2265 let _sendMMIError = (function(errorMsg, mmiServiceCode) {
michael@0 2266 options.success = false;
michael@0 2267 options.errorMsg = errorMsg;
michael@0 2268 if (mmiServiceCode) {
michael@0 2269 options.mmiServiceCode = mmiServiceCode;
michael@0 2270 }
michael@0 2271 this.sendChromeMessage(options);
michael@0 2272 }).bind(this);
michael@0 2273
michael@0 2274 function _isValidPINPUKRequest(mmiServiceCode) {
michael@0 2275 // The only allowed MMI procedure for ICC PIN, PIN2, PUK and PUK2 handling
michael@0 2276 // is "Registration" (**).
michael@0 2277 if (!mmi.procedure || mmi.procedure != MMI_PROCEDURE_REGISTRATION ) {
michael@0 2278 _sendMMIError(MMI_ERROR_KS_INVALID_ACTION, mmiServiceCode);
michael@0 2279 return false;
michael@0 2280 }
michael@0 2281
michael@0 2282 if (!mmi.sia || !mmi.sia.length || !mmi.sib || !mmi.sib.length ||
michael@0 2283 !mmi.sic || !mmi.sic.length) {
michael@0 2284 _sendMMIError(MMI_ERROR_KS_ERROR, mmiServiceCode);
michael@0 2285 return false;
michael@0 2286 }
michael@0 2287
michael@0 2288 if (mmi.sib != mmi.sic) {
michael@0 2289 _sendMMIError(MMI_ERROR_KS_MISMATCH_PIN, mmiServiceCode);
michael@0 2290 return false;
michael@0 2291 }
michael@0 2292
michael@0 2293 if (mmi.sia.length < 4 || mmi.sia.length > 8 ||
michael@0 2294 mmi.sib.length < 4 || mmi.sib.length > 8 ||
michael@0 2295 mmi.sic.length < 4 || mmi.sic.length > 8) {
michael@0 2296 _sendMMIError(MMI_ERROR_KS_INVALID_PIN, mmiServiceCode);
michael@0 2297 return false;
michael@0 2298 }
michael@0 2299
michael@0 2300 return true;
michael@0 2301 }
michael@0 2302
michael@0 2303 let _isRadioAvailable = (function(mmiServiceCode) {
michael@0 2304 if (this.radioState !== GECKO_RADIOSTATE_READY) {
michael@0 2305 _sendMMIError(GECKO_ERROR_RADIO_NOT_AVAILABLE, mmiServiceCode);
michael@0 2306 return false;
michael@0 2307 }
michael@0 2308 return true;
michael@0 2309 }).bind(this);
michael@0 2310
michael@0 2311 // If we couldn't parse the MMI code, we'll send it as an USSD request.
michael@0 2312 if (mmi === null) {
michael@0 2313 if (this._ussdSession) {
michael@0 2314 if (!_isRadioAvailable(MMI_KS_SC_USSD)) {
michael@0 2315 return;
michael@0 2316 }
michael@0 2317 options.ussd = mmiString;
michael@0 2318 this.sendUSSD(options);
michael@0 2319 return;
michael@0 2320 }
michael@0 2321
michael@0 2322 _sendMMIError(MMI_ERROR_KS_ERROR);
michael@0 2323 return;
michael@0 2324 }
michael@0 2325
michael@0 2326 if (DEBUG) {
michael@0 2327 this.context.debug("MMI " + JSON.stringify(mmi));
michael@0 2328 }
michael@0 2329
michael@0 2330 // We check if the MMI service code is supported and in that case we
michael@0 2331 // trigger the appropriate RIL request if possible.
michael@0 2332 let sc = mmi.serviceCode;
michael@0 2333 switch (sc) {
michael@0 2334 // Call forwarding
michael@0 2335 case MMI_SC_CFU:
michael@0 2336 case MMI_SC_CF_BUSY:
michael@0 2337 case MMI_SC_CF_NO_REPLY:
michael@0 2338 case MMI_SC_CF_NOT_REACHABLE:
michael@0 2339 case MMI_SC_CF_ALL:
michael@0 2340 case MMI_SC_CF_ALL_CONDITIONAL:
michael@0 2341 if (!_isRadioAvailable(MMI_KS_SC_CALL_FORWARDING)) {
michael@0 2342 return;
michael@0 2343 }
michael@0 2344 // Call forwarding requires at least an action, given by the MMI
michael@0 2345 // procedure, and a reason, given by the MMI service code, but there
michael@0 2346 // is no way that we get this far without a valid procedure or service
michael@0 2347 // code.
michael@0 2348 options.mmiServiceCode = MMI_KS_SC_CALL_FORWARDING;
michael@0 2349 options.action = MMI_PROC_TO_CF_ACTION[mmi.procedure];
michael@0 2350 options.reason = MMI_SC_TO_CF_REASON[sc];
michael@0 2351 options.number = mmi.sia;
michael@0 2352 options.serviceClass = this._siToServiceClass(mmi.sib);
michael@0 2353 if (options.action == CALL_FORWARD_ACTION_QUERY_STATUS) {
michael@0 2354 this.queryCallForwardStatus(options);
michael@0 2355 return;
michael@0 2356 }
michael@0 2357
michael@0 2358 options.isSetCallForward = true;
michael@0 2359 options.timeSeconds = mmi.sic;
michael@0 2360 this.setCallForward(options);
michael@0 2361 return;
michael@0 2362
michael@0 2363 // Change the current ICC PIN number.
michael@0 2364 case MMI_SC_PIN:
michael@0 2365 // As defined in TS.122.030 6.6.2 to change the ICC PIN we should expect
michael@0 2366 // an MMI code of the form **04*OLD_PIN*NEW_PIN*NEW_PIN#, where old PIN
michael@0 2367 // should be entered as the SIA parameter and the new PIN as SIB and
michael@0 2368 // SIC.
michael@0 2369 if (!_isRadioAvailable(MMI_KS_SC_PIN) ||
michael@0 2370 !_isValidPINPUKRequest(MMI_KS_SC_PIN)) {
michael@0 2371 return;
michael@0 2372 }
michael@0 2373
michael@0 2374 options.mmiServiceCode = MMI_KS_SC_PIN;
michael@0 2375 options.pin = mmi.sia;
michael@0 2376 options.newPin = mmi.sib;
michael@0 2377 this.changeICCPIN(options);
michael@0 2378 return;
michael@0 2379
michael@0 2380 // Change the current ICC PIN2 number.
michael@0 2381 case MMI_SC_PIN2:
michael@0 2382 // As defined in TS.122.030 6.6.2 to change the ICC PIN2 we should
michael@0 2383 // enter and MMI code of the form **042*OLD_PIN2*NEW_PIN2*NEW_PIN2#,
michael@0 2384 // where the old PIN2 should be entered as the SIA parameter and the
michael@0 2385 // new PIN2 as SIB and SIC.
michael@0 2386 if (!_isRadioAvailable(MMI_KS_SC_PIN2) ||
michael@0 2387 !_isValidPINPUKRequest(MMI_KS_SC_PIN2)) {
michael@0 2388 return;
michael@0 2389 }
michael@0 2390
michael@0 2391 options.mmiServiceCode = MMI_KS_SC_PIN2;
michael@0 2392 options.pin = mmi.sia;
michael@0 2393 options.newPin = mmi.sib;
michael@0 2394 this.changeICCPIN2(options);
michael@0 2395 return;
michael@0 2396
michael@0 2397 // Unblock ICC PIN.
michael@0 2398 case MMI_SC_PUK:
michael@0 2399 // As defined in TS.122.030 6.6.3 to unblock the ICC PIN we should
michael@0 2400 // enter an MMI code of the form **05*PUK*NEW_PIN*NEW_PIN#, where PUK
michael@0 2401 // should be entered as the SIA parameter and the new PIN as SIB and
michael@0 2402 // SIC.
michael@0 2403 if (!_isRadioAvailable(MMI_KS_SC_PUK) ||
michael@0 2404 !_isValidPINPUKRequest(MMI_KS_SC_PUK)) {
michael@0 2405 return;
michael@0 2406 }
michael@0 2407
michael@0 2408 options.mmiServiceCode = MMI_KS_SC_PUK;
michael@0 2409 options.puk = mmi.sia;
michael@0 2410 options.newPin = mmi.sib;
michael@0 2411 this.enterICCPUK(options);
michael@0 2412 return;
michael@0 2413
michael@0 2414 // Unblock ICC PIN2.
michael@0 2415 case MMI_SC_PUK2:
michael@0 2416 // As defined in TS.122.030 6.6.3 to unblock the ICC PIN2 we should
michael@0 2417 // enter an MMI code of the form **052*PUK2*NEW_PIN2*NEW_PIN2#, where
michael@0 2418 // PUK2 should be entered as the SIA parameter and the new PIN2 as SIB
michael@0 2419 // and SIC.
michael@0 2420 if (!_isRadioAvailable(MMI_KS_SC_PUK2) ||
michael@0 2421 !_isValidPINPUKRequest(MMI_KS_SC_PUK2)) {
michael@0 2422 return;
michael@0 2423 }
michael@0 2424
michael@0 2425 options.mmiServiceCode = MMI_KS_SC_PUK2;
michael@0 2426 options.puk = mmi.sia;
michael@0 2427 options.newPin = mmi.sib;
michael@0 2428 this.enterICCPUK2(options);
michael@0 2429 return;
michael@0 2430
michael@0 2431 // IMEI
michael@0 2432 case MMI_SC_IMEI:
michael@0 2433 // A device's IMEI can't change, so we only need to request it once.
michael@0 2434 if (this.IMEI == null) {
michael@0 2435 this.getIMEI(options);
michael@0 2436 return;
michael@0 2437 }
michael@0 2438 // If we already had the device's IMEI, we just send it to chrome.
michael@0 2439 options.mmiServiceCode = MMI_KS_SC_IMEI;
michael@0 2440 options.success = true;
michael@0 2441 options.statusMessage = this.IMEI;
michael@0 2442 this.sendChromeMessage(options);
michael@0 2443 return;
michael@0 2444
michael@0 2445 // CLIP
michael@0 2446 case MMI_SC_CLIP:
michael@0 2447 options.mmiServiceCode = MMI_KS_SC_CLIP;
michael@0 2448 options.procedure = mmi.procedure;
michael@0 2449 if (options.procedure === MMI_PROCEDURE_INTERROGATION) {
michael@0 2450 this.queryCLIP(options);
michael@0 2451 } else {
michael@0 2452 _sendMMIError(MMI_ERROR_KS_NOT_SUPPORTED, MMI_KS_SC_CLIP);
michael@0 2453 }
michael@0 2454 return;
michael@0 2455
michael@0 2456 // CLIR (non-temporary ones)
michael@0 2457 // TODO: Both dial() and sendMMI() functions should be unified at some
michael@0 2458 // point in the future. In the mean time we handle temporary CLIR MMI
michael@0 2459 // commands through the dial() function. Please see bug 889737.
michael@0 2460 case MMI_SC_CLIR:
michael@0 2461 options.mmiServiceCode = MMI_KS_SC_CLIR;
michael@0 2462 options.procedure = mmi.procedure;
michael@0 2463 switch (options.procedure) {
michael@0 2464 case MMI_PROCEDURE_INTERROGATION:
michael@0 2465 this.getCLIR(options);
michael@0 2466 return;
michael@0 2467 case MMI_PROCEDURE_ACTIVATION:
michael@0 2468 options.clirMode = CLIR_INVOCATION;
michael@0 2469 break;
michael@0 2470 case MMI_PROCEDURE_DEACTIVATION:
michael@0 2471 options.clirMode = CLIR_SUPPRESSION;
michael@0 2472 break;
michael@0 2473 default:
michael@0 2474 _sendMMIError(MMI_ERROR_KS_NOT_SUPPORTED, MMI_KS_SC_CLIR);
michael@0 2475 return;
michael@0 2476 }
michael@0 2477 options.isSetCLIR = true;
michael@0 2478 this.setCLIR(options);
michael@0 2479 return;
michael@0 2480
michael@0 2481 // Call barring
michael@0 2482 case MMI_SC_BAOC:
michael@0 2483 case MMI_SC_BAOIC:
michael@0 2484 case MMI_SC_BAOICxH:
michael@0 2485 case MMI_SC_BAIC:
michael@0 2486 case MMI_SC_BAICr:
michael@0 2487 case MMI_SC_BA_ALL:
michael@0 2488 case MMI_SC_BA_MO:
michael@0 2489 case MMI_SC_BA_MT:
michael@0 2490 options.mmiServiceCode = MMI_KS_SC_CALL_BARRING;
michael@0 2491 options.password = mmi.sia || "";
michael@0 2492 options.serviceClass = this._siToServiceClass(mmi.sib);
michael@0 2493 options.facility = MMI_SC_TO_CB_FACILITY[sc];
michael@0 2494 options.procedure = mmi.procedure;
michael@0 2495 if (mmi.procedure === MMI_PROCEDURE_INTERROGATION) {
michael@0 2496 this.queryICCFacilityLock(options);
michael@0 2497 return;
michael@0 2498 }
michael@0 2499 if (mmi.procedure === MMI_PROCEDURE_ACTIVATION) {
michael@0 2500 options.enabled = 1;
michael@0 2501 } else if (mmi.procedure === MMI_PROCEDURE_DEACTIVATION) {
michael@0 2502 options.enabled = 0;
michael@0 2503 } else {
michael@0 2504 _sendMMIError(MMI_ERROR_KS_NOT_SUPPORTED, MMI_KS_SC_CALL_BARRING);
michael@0 2505 return;
michael@0 2506 }
michael@0 2507 this.setICCFacilityLock(options);
michael@0 2508 return;
michael@0 2509
michael@0 2510 // Call waiting
michael@0 2511 case MMI_SC_CALL_WAITING:
michael@0 2512 if (!_isRadioAvailable(MMI_KS_SC_CALL_WAITING)) {
michael@0 2513 return;
michael@0 2514 }
michael@0 2515
michael@0 2516 options.mmiServiceCode = MMI_KS_SC_CALL_WAITING;
michael@0 2517
michael@0 2518 if (mmi.procedure === MMI_PROCEDURE_INTERROGATION) {
michael@0 2519 this._handleQueryMMICallWaiting(options);
michael@0 2520 return;
michael@0 2521 }
michael@0 2522
michael@0 2523 if (mmi.procedure === MMI_PROCEDURE_ACTIVATION) {
michael@0 2524 options.enabled = true;
michael@0 2525 } else if (mmi.procedure === MMI_PROCEDURE_DEACTIVATION) {
michael@0 2526 options.enabled = false;
michael@0 2527 } else {
michael@0 2528 _sendMMIError(MMI_ERROR_KS_NOT_SUPPORTED, MMI_KS_SC_CALL_WAITING);
michael@0 2529 return;
michael@0 2530 }
michael@0 2531
michael@0 2532 options.serviceClass = this._siToServiceClass(mmi.sia);
michael@0 2533 this._handleSetMMICallWaiting(options);
michael@0 2534 return;
michael@0 2535 }
michael@0 2536
michael@0 2537 // If the MMI code is not a known code and is a recognized USSD request,
michael@0 2538 // it shall still be sent as a USSD request.
michael@0 2539 if (mmi.fullMMI) {
michael@0 2540 if (!_isRadioAvailable(MMI_KS_SC_USSD)) {
michael@0 2541 return;
michael@0 2542 }
michael@0 2543
michael@0 2544 options.ussd = mmi.fullMMI;
michael@0 2545 options.mmiServiceCode = MMI_KS_SC_USSD;
michael@0 2546 this.sendUSSD(options);
michael@0 2547 return;
michael@0 2548 }
michael@0 2549
michael@0 2550 // At this point, the MMI string is considered as not valid MMI code and
michael@0 2551 // not valid USSD code.
michael@0 2552 _sendMMIError(MMI_ERROR_KS_ERROR);
michael@0 2553 },
michael@0 2554
michael@0 2555 /**
michael@0 2556 * Send USSD.
michael@0 2557 *
michael@0 2558 * @param ussd
michael@0 2559 * String containing the USSD code.
michael@0 2560 *
michael@0 2561 */
michael@0 2562 sendUSSD: function(options) {
michael@0 2563 let Buf = this.context.Buf;
michael@0 2564 Buf.newParcel(REQUEST_SEND_USSD, options);
michael@0 2565 Buf.writeString(options.ussd);
michael@0 2566 Buf.sendParcel();
michael@0 2567 },
michael@0 2568
michael@0 2569 /**
michael@0 2570 * Cancel pending USSD.
michael@0 2571 */
michael@0 2572 cancelUSSD: function(options) {
michael@0 2573 options.mmiServiceCode = MMI_KS_SC_USSD;
michael@0 2574 this.context.Buf.simpleRequest(REQUEST_CANCEL_USSD, options);
michael@0 2575 },
michael@0 2576
michael@0 2577 /**
michael@0 2578 * Queries current call forward rules.
michael@0 2579 *
michael@0 2580 * @param reason
michael@0 2581 * One of nsIDOMMozMobileCFInfo.CALL_FORWARD_REASON_* constants.
michael@0 2582 * @param serviceClass
michael@0 2583 * One of ICC_SERVICE_CLASS_* constants.
michael@0 2584 * @param number
michael@0 2585 * Phone number of forwarding address.
michael@0 2586 */
michael@0 2587 queryCallForwardStatus: function(options) {
michael@0 2588 let Buf = this.context.Buf;
michael@0 2589 let number = options.number || "";
michael@0 2590 Buf.newParcel(REQUEST_QUERY_CALL_FORWARD_STATUS, options);
michael@0 2591 Buf.writeInt32(CALL_FORWARD_ACTION_QUERY_STATUS);
michael@0 2592 Buf.writeInt32(options.reason);
michael@0 2593 Buf.writeInt32(options.serviceClass || ICC_SERVICE_CLASS_NONE);
michael@0 2594 Buf.writeInt32(this._toaFromString(number));
michael@0 2595 Buf.writeString(number);
michael@0 2596 Buf.writeInt32(0);
michael@0 2597 Buf.sendParcel();
michael@0 2598 },
michael@0 2599
michael@0 2600 /**
michael@0 2601 * Configures call forward rule.
michael@0 2602 *
michael@0 2603 * @param action
michael@0 2604 * One of nsIDOMMozMobileCFInfo.CALL_FORWARD_ACTION_* constants.
michael@0 2605 * @param reason
michael@0 2606 * One of nsIDOMMozMobileCFInfo.CALL_FORWARD_REASON_* constants.
michael@0 2607 * @param serviceClass
michael@0 2608 * One of ICC_SERVICE_CLASS_* constants.
michael@0 2609 * @param number
michael@0 2610 * Phone number of forwarding address.
michael@0 2611 * @param timeSeconds
michael@0 2612 * Time in seconds to wait beforec all is forwarded.
michael@0 2613 */
michael@0 2614 setCallForward: function(options) {
michael@0 2615 let Buf = this.context.Buf;
michael@0 2616 Buf.newParcel(REQUEST_SET_CALL_FORWARD, options);
michael@0 2617 Buf.writeInt32(options.action);
michael@0 2618 Buf.writeInt32(options.reason);
michael@0 2619 Buf.writeInt32(options.serviceClass);
michael@0 2620 Buf.writeInt32(this._toaFromString(options.number));
michael@0 2621 Buf.writeString(options.number);
michael@0 2622 Buf.writeInt32(options.timeSeconds);
michael@0 2623 Buf.sendParcel();
michael@0 2624 },
michael@0 2625
michael@0 2626 /**
michael@0 2627 * Queries current call barring rules.
michael@0 2628 *
michael@0 2629 * @param program
michael@0 2630 * One of nsIDOMMozMobileConnection.CALL_BARRING_PROGRAM_* constants.
michael@0 2631 * @param serviceClass
michael@0 2632 * One of ICC_SERVICE_CLASS_* constants.
michael@0 2633 */
michael@0 2634 queryCallBarringStatus: function(options) {
michael@0 2635 options.facility = CALL_BARRING_PROGRAM_TO_FACILITY[options.program];
michael@0 2636 options.password = ""; // For query no need to provide it.
michael@0 2637 this.queryICCFacilityLock(options);
michael@0 2638 },
michael@0 2639
michael@0 2640 /**
michael@0 2641 * Configures call barring rule.
michael@0 2642 *
michael@0 2643 * @param program
michael@0 2644 * One of nsIDOMMozMobileConnection.CALL_BARRING_PROGRAM_* constants.
michael@0 2645 * @param enabled
michael@0 2646 * Enable or disable the call barring.
michael@0 2647 * @param password
michael@0 2648 * Barring password.
michael@0 2649 * @param serviceClass
michael@0 2650 * One of ICC_SERVICE_CLASS_* constants.
michael@0 2651 */
michael@0 2652 setCallBarring: function(options) {
michael@0 2653 options.facility = CALL_BARRING_PROGRAM_TO_FACILITY[options.program];
michael@0 2654 this.setICCFacilityLock(options);
michael@0 2655 },
michael@0 2656
michael@0 2657 /**
michael@0 2658 * Change call barring facility password.
michael@0 2659 *
michael@0 2660 * @param pin
michael@0 2661 * Old password.
michael@0 2662 * @param newPin
michael@0 2663 * New password.
michael@0 2664 */
michael@0 2665 changeCallBarringPassword: function(options) {
michael@0 2666 let Buf = this.context.Buf;
michael@0 2667 Buf.newParcel(REQUEST_CHANGE_BARRING_PASSWORD, options);
michael@0 2668 Buf.writeInt32(3);
michael@0 2669 // Set facility to ICC_CB_FACILITY_BA_ALL by following TS.22.030 clause
michael@0 2670 // 6.5.4 and Table B.1.
michael@0 2671 Buf.writeString(ICC_CB_FACILITY_BA_ALL);
michael@0 2672 Buf.writeString(options.pin);
michael@0 2673 Buf.writeString(options.newPin);
michael@0 2674 Buf.sendParcel();
michael@0 2675 },
michael@0 2676
michael@0 2677 /**
michael@0 2678 * Handle STK CALL_SET_UP request.
michael@0 2679 *
michael@0 2680 * @param hasConfirmed
michael@0 2681 * Does use have confirmed the call requested from ICC?
michael@0 2682 */
michael@0 2683 stkHandleCallSetup: function(options) {
michael@0 2684 let Buf = this.context.Buf;
michael@0 2685 Buf.newParcel(REQUEST_STK_HANDLE_CALL_SETUP_REQUESTED_FROM_SIM);
michael@0 2686 Buf.writeInt32(1);
michael@0 2687 Buf.writeInt32(options.hasConfirmed ? 1 : 0);
michael@0 2688 Buf.sendParcel();
michael@0 2689 },
michael@0 2690
michael@0 2691 /**
michael@0 2692 * Send STK Profile Download.
michael@0 2693 *
michael@0 2694 * @param profile Profile supported by ME.
michael@0 2695 */
michael@0 2696 sendStkTerminalProfile: function(profile) {
michael@0 2697 let Buf = this.context.Buf;
michael@0 2698 let GsmPDUHelper = this.context.GsmPDUHelper;
michael@0 2699
michael@0 2700 Buf.newParcel(REQUEST_STK_SET_PROFILE);
michael@0 2701 Buf.writeInt32(profile.length * 2);
michael@0 2702 for (let i = 0; i < profile.length; i++) {
michael@0 2703 GsmPDUHelper.writeHexOctet(profile[i]);
michael@0 2704 }
michael@0 2705 Buf.writeInt32(0);
michael@0 2706 Buf.sendParcel();
michael@0 2707 },
michael@0 2708
michael@0 2709 /**
michael@0 2710 * Send STK terminal response.
michael@0 2711 *
michael@0 2712 * @param command
michael@0 2713 * @param deviceIdentities
michael@0 2714 * @param resultCode
michael@0 2715 * @param [optional] itemIdentifier
michael@0 2716 * @param [optional] input
michael@0 2717 * @param [optional] isYesNo
michael@0 2718 * @param [optional] hasConfirmed
michael@0 2719 * @param [optional] localInfo
michael@0 2720 * @param [optional] timer
michael@0 2721 */
michael@0 2722 sendStkTerminalResponse: function(response) {
michael@0 2723 if (response.hasConfirmed !== undefined) {
michael@0 2724 this.stkHandleCallSetup(response);
michael@0 2725 return;
michael@0 2726 }
michael@0 2727
michael@0 2728 let Buf = this.context.Buf;
michael@0 2729 let ComprehensionTlvHelper = this.context.ComprehensionTlvHelper;
michael@0 2730 let GsmPDUHelper = this.context.GsmPDUHelper;
michael@0 2731
michael@0 2732 let command = response.command;
michael@0 2733 Buf.newParcel(REQUEST_STK_SEND_TERMINAL_RESPONSE);
michael@0 2734
michael@0 2735 // 1st mark for Parcel size
michael@0 2736 Buf.startCalOutgoingSize(function(size) {
michael@0 2737 // Parcel size is in string length, which costs 2 uint8 per char.
michael@0 2738 Buf.writeInt32(size / 2);
michael@0 2739 });
michael@0 2740
michael@0 2741 // Command Details
michael@0 2742 GsmPDUHelper.writeHexOctet(COMPREHENSIONTLV_TAG_COMMAND_DETAILS |
michael@0 2743 COMPREHENSIONTLV_FLAG_CR);
michael@0 2744 GsmPDUHelper.writeHexOctet(3);
michael@0 2745 if (response.command) {
michael@0 2746 GsmPDUHelper.writeHexOctet(command.commandNumber);
michael@0 2747 GsmPDUHelper.writeHexOctet(command.typeOfCommand);
michael@0 2748 GsmPDUHelper.writeHexOctet(command.commandQualifier);
michael@0 2749 } else {
michael@0 2750 GsmPDUHelper.writeHexOctet(0x00);
michael@0 2751 GsmPDUHelper.writeHexOctet(0x00);
michael@0 2752 GsmPDUHelper.writeHexOctet(0x00);
michael@0 2753 }
michael@0 2754
michael@0 2755 // Device Identifier
michael@0 2756 // According to TS102.223/TS31.111 section 6.8 Structure of
michael@0 2757 // TERMINAL RESPONSE, "For all SIMPLE-TLV objects with Min=N,
michael@0 2758 // the ME should set the CR(comprehension required) flag to
michael@0 2759 // comprehension not required.(CR=0)"
michael@0 2760 // Since DEVICE_IDENTITIES and DURATION TLVs have Min=N,
michael@0 2761 // the CR flag is not set.
michael@0 2762 GsmPDUHelper.writeHexOctet(COMPREHENSIONTLV_TAG_DEVICE_ID);
michael@0 2763 GsmPDUHelper.writeHexOctet(2);
michael@0 2764 GsmPDUHelper.writeHexOctet(STK_DEVICE_ID_ME);
michael@0 2765 GsmPDUHelper.writeHexOctet(STK_DEVICE_ID_SIM);
michael@0 2766
michael@0 2767 // Result
michael@0 2768 GsmPDUHelper.writeHexOctet(COMPREHENSIONTLV_TAG_RESULT |
michael@0 2769 COMPREHENSIONTLV_FLAG_CR);
michael@0 2770 GsmPDUHelper.writeHexOctet(1);
michael@0 2771 GsmPDUHelper.writeHexOctet(response.resultCode);
michael@0 2772
michael@0 2773 // Item Identifier
michael@0 2774 if (response.itemIdentifier != null) {
michael@0 2775 GsmPDUHelper.writeHexOctet(COMPREHENSIONTLV_TAG_ITEM_ID |
michael@0 2776 COMPREHENSIONTLV_FLAG_CR);
michael@0 2777 GsmPDUHelper.writeHexOctet(1);
michael@0 2778 GsmPDUHelper.writeHexOctet(response.itemIdentifier);
michael@0 2779 }
michael@0 2780
michael@0 2781 // No need to process Text data if user requests help information.
michael@0 2782 if (response.resultCode != STK_RESULT_HELP_INFO_REQUIRED) {
michael@0 2783 let text;
michael@0 2784 if (response.isYesNo !== undefined) {
michael@0 2785 // GET_INKEY
michael@0 2786 // When the ME issues a successful TERMINAL RESPONSE for a GET INKEY
michael@0 2787 // ("Yes/No") command with command qualifier set to "Yes/No", it shall
michael@0 2788 // supply the value '01' when the answer is "positive" and the value
michael@0 2789 // '00' when the answer is "negative" in the Text string data object.
michael@0 2790 text = response.isYesNo ? 0x01 : 0x00;
michael@0 2791 } else {
michael@0 2792 text = response.input;
michael@0 2793 }
michael@0 2794
michael@0 2795 if (text) {
michael@0 2796 GsmPDUHelper.writeHexOctet(COMPREHENSIONTLV_TAG_TEXT_STRING |
michael@0 2797 COMPREHENSIONTLV_FLAG_CR);
michael@0 2798
michael@0 2799 // 2nd mark for text length
michael@0 2800 Buf.startCalOutgoingSize(function(size) {
michael@0 2801 // Text length is in number of hexOctets, which costs 4 uint8 per hexOctet.
michael@0 2802 GsmPDUHelper.writeHexOctet(size / 4);
michael@0 2803 });
michael@0 2804
michael@0 2805 let coding = command.options.isUCS2 ?
michael@0 2806 STK_TEXT_CODING_UCS2 :
michael@0 2807 (command.options.isPacked ?
michael@0 2808 STK_TEXT_CODING_GSM_7BIT_PACKED :
michael@0 2809 STK_TEXT_CODING_GSM_8BIT);
michael@0 2810 GsmPDUHelper.writeHexOctet(coding);
michael@0 2811
michael@0 2812 // Write Text String.
michael@0 2813 switch (coding) {
michael@0 2814 case STK_TEXT_CODING_UCS2:
michael@0 2815 GsmPDUHelper.writeUCS2String(text);
michael@0 2816 break;
michael@0 2817 case STK_TEXT_CODING_GSM_7BIT_PACKED:
michael@0 2818 GsmPDUHelper.writeStringAsSeptets(text, 0, 0, 0);
michael@0 2819 break;
michael@0 2820 case STK_TEXT_CODING_GSM_8BIT:
michael@0 2821 for (let i = 0; i < text.length; i++) {
michael@0 2822 GsmPDUHelper.writeHexOctet(text.charCodeAt(i));
michael@0 2823 }
michael@0 2824 break;
michael@0 2825 }
michael@0 2826
michael@0 2827 // Calculate and write text length to 2nd mark
michael@0 2828 Buf.stopCalOutgoingSize();
michael@0 2829 }
michael@0 2830 }
michael@0 2831
michael@0 2832 // Local Information
michael@0 2833 if (response.localInfo) {
michael@0 2834 let localInfo = response.localInfo;
michael@0 2835
michael@0 2836 // Location Infomation
michael@0 2837 if (localInfo.locationInfo) {
michael@0 2838 ComprehensionTlvHelper.writeLocationInfoTlv(localInfo.locationInfo);
michael@0 2839 }
michael@0 2840
michael@0 2841 // IMEI
michael@0 2842 if (localInfo.imei != null) {
michael@0 2843 let imei = localInfo.imei;
michael@0 2844 if (imei.length == 15) {
michael@0 2845 imei = imei + "0";
michael@0 2846 }
michael@0 2847
michael@0 2848 GsmPDUHelper.writeHexOctet(COMPREHENSIONTLV_TAG_IMEI);
michael@0 2849 GsmPDUHelper.writeHexOctet(8);
michael@0 2850 for (let i = 0; i < imei.length / 2; i++) {
michael@0 2851 GsmPDUHelper.writeHexOctet(parseInt(imei.substr(i * 2, 2), 16));
michael@0 2852 }
michael@0 2853 }
michael@0 2854
michael@0 2855 // Date and Time Zone
michael@0 2856 if (localInfo.date != null) {
michael@0 2857 ComprehensionTlvHelper.writeDateTimeZoneTlv(localInfo.date);
michael@0 2858 }
michael@0 2859
michael@0 2860 // Language
michael@0 2861 if (localInfo.language) {
michael@0 2862 ComprehensionTlvHelper.writeLanguageTlv(localInfo.language);
michael@0 2863 }
michael@0 2864 }
michael@0 2865
michael@0 2866 // Timer
michael@0 2867 if (response.timer) {
michael@0 2868 let timer = response.timer;
michael@0 2869
michael@0 2870 if (timer.timerId) {
michael@0 2871 GsmPDUHelper.writeHexOctet(COMPREHENSIONTLV_TAG_TIMER_IDENTIFIER);
michael@0 2872 GsmPDUHelper.writeHexOctet(1);
michael@0 2873 GsmPDUHelper.writeHexOctet(timer.timerId);
michael@0 2874 }
michael@0 2875
michael@0 2876 if (timer.timerValue) {
michael@0 2877 ComprehensionTlvHelper.writeTimerValueTlv(timer.timerValue, false);
michael@0 2878 }
michael@0 2879 }
michael@0 2880
michael@0 2881 // Calculate and write Parcel size to 1st mark
michael@0 2882 Buf.stopCalOutgoingSize();
michael@0 2883
michael@0 2884 Buf.writeInt32(0);
michael@0 2885 Buf.sendParcel();
michael@0 2886 },
michael@0 2887
michael@0 2888 /**
michael@0 2889 * Send STK Envelope(Menu Selection) command.
michael@0 2890 *
michael@0 2891 * @param itemIdentifier
michael@0 2892 * @param helpRequested
michael@0 2893 */
michael@0 2894 sendStkMenuSelection: function(command) {
michael@0 2895 command.tag = BER_MENU_SELECTION_TAG;
michael@0 2896 command.deviceId = {
michael@0 2897 sourceId :STK_DEVICE_ID_KEYPAD,
michael@0 2898 destinationId: STK_DEVICE_ID_SIM
michael@0 2899 };
michael@0 2900 this.sendICCEnvelopeCommand(command);
michael@0 2901 },
michael@0 2902
michael@0 2903 /**
michael@0 2904 * Send STK Envelope(Timer Expiration) command.
michael@0 2905 *
michael@0 2906 * @param timer
michael@0 2907 */
michael@0 2908 sendStkTimerExpiration: function(command) {
michael@0 2909 command.tag = BER_TIMER_EXPIRATION_TAG;
michael@0 2910 command.deviceId = {
michael@0 2911 sourceId: STK_DEVICE_ID_ME,
michael@0 2912 destinationId: STK_DEVICE_ID_SIM
michael@0 2913 };
michael@0 2914 command.timerId = command.timer.timerId;
michael@0 2915 command.timerValue = command.timer.timerValue;
michael@0 2916 this.sendICCEnvelopeCommand(command);
michael@0 2917 },
michael@0 2918
michael@0 2919 /**
michael@0 2920 * Send STK Envelope(Event Download) command.
michael@0 2921 * @param event
michael@0 2922 */
michael@0 2923 sendStkEventDownload: function(command) {
michael@0 2924 command.tag = BER_EVENT_DOWNLOAD_TAG;
michael@0 2925 command.eventList = command.event.eventType;
michael@0 2926 switch (command.eventList) {
michael@0 2927 case STK_EVENT_TYPE_LOCATION_STATUS:
michael@0 2928 command.deviceId = {
michael@0 2929 sourceId :STK_DEVICE_ID_ME,
michael@0 2930 destinationId: STK_DEVICE_ID_SIM
michael@0 2931 };
michael@0 2932 command.locationStatus = command.event.locationStatus;
michael@0 2933 // Location info should only be provided when locationStatus is normal.
michael@0 2934 if (command.locationStatus == STK_SERVICE_STATE_NORMAL) {
michael@0 2935 command.locationInfo = command.event.locationInfo;
michael@0 2936 }
michael@0 2937 break;
michael@0 2938 case STK_EVENT_TYPE_MT_CALL:
michael@0 2939 command.deviceId = {
michael@0 2940 sourceId: STK_DEVICE_ID_NETWORK,
michael@0 2941 destinationId: STK_DEVICE_ID_SIM
michael@0 2942 };
michael@0 2943 command.transactionId = 0;
michael@0 2944 command.address = command.event.number;
michael@0 2945 break;
michael@0 2946 case STK_EVENT_TYPE_CALL_DISCONNECTED:
michael@0 2947 command.cause = command.event.error;
michael@0 2948 // Fall through.
michael@0 2949 case STK_EVENT_TYPE_CALL_CONNECTED:
michael@0 2950 command.deviceId = {
michael@0 2951 sourceId: (command.event.isIssuedByRemote ?
michael@0 2952 STK_DEVICE_ID_NETWORK : STK_DEVICE_ID_ME),
michael@0 2953 destinationId: STK_DEVICE_ID_SIM
michael@0 2954 };
michael@0 2955 command.transactionId = 0;
michael@0 2956 break;
michael@0 2957 case STK_EVENT_TYPE_USER_ACTIVITY:
michael@0 2958 command.deviceId = {
michael@0 2959 sourceId: STK_DEVICE_ID_ME,
michael@0 2960 destinationId: STK_DEVICE_ID_SIM
michael@0 2961 };
michael@0 2962 break;
michael@0 2963 case STK_EVENT_TYPE_IDLE_SCREEN_AVAILABLE:
michael@0 2964 command.deviceId = {
michael@0 2965 sourceId: STK_DEVICE_ID_DISPLAY,
michael@0 2966 destinationId: STK_DEVICE_ID_SIM
michael@0 2967 };
michael@0 2968 break;
michael@0 2969 case STK_EVENT_TYPE_LANGUAGE_SELECTION:
michael@0 2970 command.deviceId = {
michael@0 2971 sourceId: STK_DEVICE_ID_ME,
michael@0 2972 destinationId: STK_DEVICE_ID_SIM
michael@0 2973 };
michael@0 2974 command.language = command.event.language;
michael@0 2975 break;
michael@0 2976 case STK_EVENT_TYPE_BROWSER_TERMINATION:
michael@0 2977 command.deviceId = {
michael@0 2978 sourceId: STK_DEVICE_ID_ME,
michael@0 2979 destinationId: STK_DEVICE_ID_SIM
michael@0 2980 };
michael@0 2981 command.terminationCause = command.event.terminationCause;
michael@0 2982 break;
michael@0 2983 }
michael@0 2984 this.sendICCEnvelopeCommand(command);
michael@0 2985 },
michael@0 2986
michael@0 2987 /**
michael@0 2988 * Send REQUEST_STK_SEND_ENVELOPE_COMMAND to ICC.
michael@0 2989 *
michael@0 2990 * @param tag
michael@0 2991 * @patam deviceId
michael@0 2992 * @param [optioanl] itemIdentifier
michael@0 2993 * @param [optional] helpRequested
michael@0 2994 * @param [optional] eventList
michael@0 2995 * @param [optional] locationStatus
michael@0 2996 * @param [optional] locationInfo
michael@0 2997 * @param [optional] address
michael@0 2998 * @param [optional] transactionId
michael@0 2999 * @param [optional] cause
michael@0 3000 * @param [optional] timerId
michael@0 3001 * @param [optional] timerValue
michael@0 3002 * @param [optional] terminationCause
michael@0 3003 */
michael@0 3004 sendICCEnvelopeCommand: function(options) {
michael@0 3005 if (DEBUG) {
michael@0 3006 this.context.debug("Stk Envelope " + JSON.stringify(options));
michael@0 3007 }
michael@0 3008
michael@0 3009 let Buf = this.context.Buf;
michael@0 3010 let ComprehensionTlvHelper = this.context.ComprehensionTlvHelper;
michael@0 3011 let GsmPDUHelper = this.context.GsmPDUHelper;
michael@0 3012
michael@0 3013 Buf.newParcel(REQUEST_STK_SEND_ENVELOPE_COMMAND);
michael@0 3014
michael@0 3015 // 1st mark for Parcel size
michael@0 3016 Buf.startCalOutgoingSize(function(size) {
michael@0 3017 // Parcel size is in string length, which costs 2 uint8 per char.
michael@0 3018 Buf.writeInt32(size / 2);
michael@0 3019 });
michael@0 3020
michael@0 3021 // Write a BER-TLV
michael@0 3022 GsmPDUHelper.writeHexOctet(options.tag);
michael@0 3023 // 2nd mark for BER length
michael@0 3024 Buf.startCalOutgoingSize(function(size) {
michael@0 3025 // BER length is in number of hexOctets, which costs 4 uint8 per hexOctet.
michael@0 3026 GsmPDUHelper.writeHexOctet(size / 4);
michael@0 3027 });
michael@0 3028
michael@0 3029 // Device Identifies
michael@0 3030 GsmPDUHelper.writeHexOctet(COMPREHENSIONTLV_TAG_DEVICE_ID |
michael@0 3031 COMPREHENSIONTLV_FLAG_CR);
michael@0 3032 GsmPDUHelper.writeHexOctet(2);
michael@0 3033 GsmPDUHelper.writeHexOctet(options.deviceId.sourceId);
michael@0 3034 GsmPDUHelper.writeHexOctet(options.deviceId.destinationId);
michael@0 3035
michael@0 3036 // Item Identifier
michael@0 3037 if (options.itemIdentifier != null) {
michael@0 3038 GsmPDUHelper.writeHexOctet(COMPREHENSIONTLV_TAG_ITEM_ID |
michael@0 3039 COMPREHENSIONTLV_FLAG_CR);
michael@0 3040 GsmPDUHelper.writeHexOctet(1);
michael@0 3041 GsmPDUHelper.writeHexOctet(options.itemIdentifier);
michael@0 3042 }
michael@0 3043
michael@0 3044 // Help Request
michael@0 3045 if (options.helpRequested) {
michael@0 3046 GsmPDUHelper.writeHexOctet(COMPREHENSIONTLV_TAG_HELP_REQUEST |
michael@0 3047 COMPREHENSIONTLV_FLAG_CR);
michael@0 3048 GsmPDUHelper.writeHexOctet(0);
michael@0 3049 // Help Request doesn't have value
michael@0 3050 }
michael@0 3051
michael@0 3052 // Event List
michael@0 3053 if (options.eventList != null) {
michael@0 3054 GsmPDUHelper.writeHexOctet(COMPREHENSIONTLV_TAG_EVENT_LIST |
michael@0 3055 COMPREHENSIONTLV_FLAG_CR);
michael@0 3056 GsmPDUHelper.writeHexOctet(1);
michael@0 3057 GsmPDUHelper.writeHexOctet(options.eventList);
michael@0 3058 }
michael@0 3059
michael@0 3060 // Location Status
michael@0 3061 if (options.locationStatus != null) {
michael@0 3062 let len = options.locationStatus.length;
michael@0 3063 GsmPDUHelper.writeHexOctet(COMPREHENSIONTLV_TAG_LOCATION_STATUS |
michael@0 3064 COMPREHENSIONTLV_FLAG_CR);
michael@0 3065 GsmPDUHelper.writeHexOctet(1);
michael@0 3066 GsmPDUHelper.writeHexOctet(options.locationStatus);
michael@0 3067 }
michael@0 3068
michael@0 3069 // Location Info
michael@0 3070 if (options.locationInfo) {
michael@0 3071 ComprehensionTlvHelper.writeLocationInfoTlv(options.locationInfo);
michael@0 3072 }
michael@0 3073
michael@0 3074 // Transaction Id
michael@0 3075 if (options.transactionId != null) {
michael@0 3076 GsmPDUHelper.writeHexOctet(COMPREHENSIONTLV_TAG_TRANSACTION_ID |
michael@0 3077 COMPREHENSIONTLV_FLAG_CR);
michael@0 3078 GsmPDUHelper.writeHexOctet(1);
michael@0 3079 GsmPDUHelper.writeHexOctet(options.transactionId);
michael@0 3080 }
michael@0 3081
michael@0 3082 // Address
michael@0 3083 if (options.address) {
michael@0 3084 GsmPDUHelper.writeHexOctet(COMPREHENSIONTLV_TAG_ADDRESS |
michael@0 3085 COMPREHENSIONTLV_FLAG_CR);
michael@0 3086 ComprehensionTlvHelper.writeLength(
michael@0 3087 Math.ceil(options.address.length/2) + 1 // address BCD + TON
michael@0 3088 );
michael@0 3089 this.context.ICCPDUHelper.writeDiallingNumber(options.address);
michael@0 3090 }
michael@0 3091
michael@0 3092 // Cause of disconnection.
michael@0 3093 if (options.cause != null) {
michael@0 3094 ComprehensionTlvHelper.writeCauseTlv(options.cause);
michael@0 3095 }
michael@0 3096
michael@0 3097 // Timer Identifier
michael@0 3098 if (options.timerId != null) {
michael@0 3099 GsmPDUHelper.writeHexOctet(COMPREHENSIONTLV_TAG_TIMER_IDENTIFIER |
michael@0 3100 COMPREHENSIONTLV_FLAG_CR);
michael@0 3101 GsmPDUHelper.writeHexOctet(1);
michael@0 3102 GsmPDUHelper.writeHexOctet(options.timerId);
michael@0 3103 }
michael@0 3104
michael@0 3105 // Timer Value
michael@0 3106 if (options.timerValue != null) {
michael@0 3107 ComprehensionTlvHelper.writeTimerValueTlv(options.timerValue, true);
michael@0 3108 }
michael@0 3109
michael@0 3110 // Language
michael@0 3111 if (options.language) {
michael@0 3112 ComprehensionTlvHelper.writeLanguageTlv(options.language);
michael@0 3113 }
michael@0 3114
michael@0 3115 // Browser Termination
michael@0 3116 if (options.terminationCause != null) {
michael@0 3117 GsmPDUHelper.writeHexOctet(COMPREHENSIONTLV_TAG_BROWSER_TERMINATION_CAUSE |
michael@0 3118 COMPREHENSIONTLV_FLAG_CR);
michael@0 3119 GsmPDUHelper.writeHexOctet(1);
michael@0 3120 GsmPDUHelper.writeHexOctet(options.terminationCause);
michael@0 3121 }
michael@0 3122
michael@0 3123 // Calculate and write BER length to 2nd mark
michael@0 3124 Buf.stopCalOutgoingSize();
michael@0 3125
michael@0 3126 // Calculate and write Parcel size to 1st mark
michael@0 3127 Buf.stopCalOutgoingSize();
michael@0 3128
michael@0 3129 Buf.writeInt32(0);
michael@0 3130 Buf.sendParcel();
michael@0 3131 },
michael@0 3132
michael@0 3133 /**
michael@0 3134 * Check a given number against the list of emergency numbers provided by the RIL.
michael@0 3135 *
michael@0 3136 * @param number
michael@0 3137 * The number to look up.
michael@0 3138 */
michael@0 3139 _isEmergencyNumber: function(number) {
michael@0 3140 // Check ril provided numbers first.
michael@0 3141 let numbers = RIL_EMERGENCY_NUMBERS;
michael@0 3142
michael@0 3143 if (numbers) {
michael@0 3144 numbers = numbers.split(",");
michael@0 3145 } else {
michael@0 3146 // No ecclist system property, so use our own list.
michael@0 3147 numbers = DEFAULT_EMERGENCY_NUMBERS;
michael@0 3148 }
michael@0 3149
michael@0 3150 return numbers.indexOf(number) != -1;
michael@0 3151 },
michael@0 3152
michael@0 3153 /**
michael@0 3154 * Checks whether to temporarily suppress caller id for the call.
michael@0 3155 *
michael@0 3156 * @param mmi
michael@0 3157 * MMI full object.
michael@0 3158 */
michael@0 3159 _isTemporaryModeCLIR: function(mmi) {
michael@0 3160 return (mmi &&
michael@0 3161 mmi.serviceCode == MMI_SC_CLIR &&
michael@0 3162 mmi.dialNumber &&
michael@0 3163 (mmi.procedure == MMI_PROCEDURE_ACTIVATION ||
michael@0 3164 mmi.procedure == MMI_PROCEDURE_DEACTIVATION));
michael@0 3165 },
michael@0 3166
michael@0 3167 /**
michael@0 3168 * Report STK Service is running.
michael@0 3169 */
michael@0 3170 reportStkServiceIsRunning: function() {
michael@0 3171 this.context.Buf.simpleRequest(REQUEST_REPORT_STK_SERVICE_IS_RUNNING);
michael@0 3172 },
michael@0 3173
michael@0 3174 /**
michael@0 3175 * Process ICC status.
michael@0 3176 */
michael@0 3177 _processICCStatus: function(iccStatus) {
michael@0 3178 // If |_waitingRadioTech| is true, we should not get app information because
michael@0 3179 // the |_isCdma| flag is not ready yet. Otherwise we may use wrong index to
michael@0 3180 // get app information, especially for the case that icc card has both cdma
michael@0 3181 // and gsm subscription.
michael@0 3182 if (this._waitingRadioTech) {
michael@0 3183 return;
michael@0 3184 }
michael@0 3185
michael@0 3186 this.iccStatus = iccStatus;
michael@0 3187 let newCardState;
michael@0 3188 let index = this._isCdma ? iccStatus.cdmaSubscriptionAppIndex :
michael@0 3189 iccStatus.gsmUmtsSubscriptionAppIndex;
michael@0 3190 let app = iccStatus.apps[index];
michael@0 3191
michael@0 3192 // When |iccStatus.cardState| is not CARD_STATE_PRESENT or have incorrect
michael@0 3193 // app information, we can not get iccId. So treat ICC as undetected.
michael@0 3194 if (iccStatus.cardState !== CARD_STATE_PRESENT || !app) {
michael@0 3195 if (this.cardState !== GECKO_CARDSTATE_UNDETECTED) {
michael@0 3196 this.operator = null;
michael@0 3197 // We should send |cardstatechange| before |iccinfochange|, otherwise we
michael@0 3198 // may lost cardstatechange event when icc card becomes undetected.
michael@0 3199 this.cardState = GECKO_CARDSTATE_UNDETECTED;
michael@0 3200 this.sendChromeMessage({rilMessageType: "cardstatechange",
michael@0 3201 cardState: this.cardState});
michael@0 3202
michael@0 3203 this.iccInfo = {iccType: null};
michael@0 3204 this.context.ICCUtilsHelper.handleICCInfoChange();
michael@0 3205 }
michael@0 3206 return;
michael@0 3207 }
michael@0 3208
michael@0 3209 let ICCRecordHelper = this.context.ICCRecordHelper;
michael@0 3210 // fetchICCRecords will need to read aid, so read aid here.
michael@0 3211 this.aid = app.aid;
michael@0 3212 this.appType = app.app_type;
michael@0 3213 this.iccInfo.iccType = GECKO_CARD_TYPE[this.appType];
michael@0 3214 // Try to get iccId only when cardState left GECKO_CARDSTATE_UNDETECTED.
michael@0 3215 if (iccStatus.cardState === CARD_STATE_PRESENT &&
michael@0 3216 (this.cardState === GECKO_CARDSTATE_UNINITIALIZED ||
michael@0 3217 this.cardState === GECKO_CARDSTATE_UNDETECTED)) {
michael@0 3218 ICCRecordHelper.readICCID();
michael@0 3219 }
michael@0 3220
michael@0 3221 switch (app.app_state) {
michael@0 3222 case CARD_APPSTATE_ILLEGAL:
michael@0 3223 newCardState = GECKO_CARDSTATE_ILLEGAL;
michael@0 3224 break;
michael@0 3225 case CARD_APPSTATE_PIN:
michael@0 3226 newCardState = GECKO_CARDSTATE_PIN_REQUIRED;
michael@0 3227 break;
michael@0 3228 case CARD_APPSTATE_PUK:
michael@0 3229 newCardState = GECKO_CARDSTATE_PUK_REQUIRED;
michael@0 3230 break;
michael@0 3231 case CARD_APPSTATE_SUBSCRIPTION_PERSO:
michael@0 3232 newCardState = PERSONSUBSTATE[app.perso_substate];
michael@0 3233 break;
michael@0 3234 case CARD_APPSTATE_READY:
michael@0 3235 newCardState = GECKO_CARDSTATE_READY;
michael@0 3236 break;
michael@0 3237 case CARD_APPSTATE_UNKNOWN:
michael@0 3238 case CARD_APPSTATE_DETECTED:
michael@0 3239 // Fall through.
michael@0 3240 default:
michael@0 3241 newCardState = GECKO_CARDSTATE_UNKNOWN;
michael@0 3242 }
michael@0 3243
michael@0 3244 let pin1State = app.pin1_replaced ? iccStatus.universalPINState :
michael@0 3245 app.pin1;
michael@0 3246 if (pin1State === CARD_PINSTATE_ENABLED_PERM_BLOCKED) {
michael@0 3247 newCardState = GECKO_CARDSTATE_PERMANENT_BLOCKED;
michael@0 3248 }
michael@0 3249
michael@0 3250 if (this.cardState == newCardState) {
michael@0 3251 return;
michael@0 3252 }
michael@0 3253
michael@0 3254 // This was moved down from CARD_APPSTATE_READY
michael@0 3255 this.requestNetworkInfo();
michael@0 3256 if (newCardState == GECKO_CARDSTATE_READY) {
michael@0 3257 // For type SIM, we need to check EF_phase first.
michael@0 3258 // Other types of ICC we can send Terminal_Profile immediately.
michael@0 3259 if (this.appType == CARD_APPTYPE_SIM) {
michael@0 3260 this.context.SimRecordHelper.readSimPhase();
michael@0 3261 } else if (RILQUIRKS_SEND_STK_PROFILE_DOWNLOAD) {
michael@0 3262 this.sendStkTerminalProfile(STK_SUPPORTED_TERMINAL_PROFILE);
michael@0 3263 }
michael@0 3264
michael@0 3265 ICCRecordHelper.fetchICCRecords();
michael@0 3266 }
michael@0 3267
michael@0 3268 this.cardState = newCardState;
michael@0 3269 this.sendChromeMessage({rilMessageType: "cardstatechange",
michael@0 3270 cardState: this.cardState});
michael@0 3271 },
michael@0 3272
michael@0 3273 /**
michael@0 3274 * Helper for processing responses of functions such as enterICC* and changeICC*.
michael@0 3275 */
michael@0 3276 _processEnterAndChangeICCResponses: function(length, options) {
michael@0 3277 options.success = (options.rilRequestError === 0);
michael@0 3278 if (!options.success) {
michael@0 3279 options.errorMsg = RIL_ERROR_TO_GECKO_ERROR[options.rilRequestError];
michael@0 3280 }
michael@0 3281 options.retryCount = length ? this.context.Buf.readInt32List()[0] : -1;
michael@0 3282 if (options.rilMessageType != "sendMMI") {
michael@0 3283 this.sendChromeMessage(options);
michael@0 3284 return;
michael@0 3285 }
michael@0 3286
michael@0 3287 let mmiServiceCode = options.mmiServiceCode;
michael@0 3288
michael@0 3289 if (options.success) {
michael@0 3290 switch (mmiServiceCode) {
michael@0 3291 case MMI_KS_SC_PIN:
michael@0 3292 options.statusMessage = MMI_SM_KS_PIN_CHANGED;
michael@0 3293 break;
michael@0 3294 case MMI_KS_SC_PIN2:
michael@0 3295 options.statusMessage = MMI_SM_KS_PIN2_CHANGED;
michael@0 3296 break;
michael@0 3297 case MMI_KS_SC_PUK:
michael@0 3298 options.statusMessage = MMI_SM_KS_PIN_UNBLOCKED;
michael@0 3299 break;
michael@0 3300 case MMI_KS_SC_PUK2:
michael@0 3301 options.statusMessage = MMI_SM_KS_PIN2_UNBLOCKED;
michael@0 3302 break;
michael@0 3303 }
michael@0 3304 } else {
michael@0 3305 if (options.retryCount <= 0) {
michael@0 3306 if (mmiServiceCode === MMI_KS_SC_PUK) {
michael@0 3307 options.errorMsg = MMI_ERROR_KS_SIM_BLOCKED;
michael@0 3308 } else if (mmiServiceCode === MMI_KS_SC_PIN) {
michael@0 3309 options.errorMsg = MMI_ERROR_KS_NEEDS_PUK;
michael@0 3310 }
michael@0 3311 } else {
michael@0 3312 if (mmiServiceCode === MMI_KS_SC_PIN ||
michael@0 3313 mmiServiceCode === MMI_KS_SC_PIN2) {
michael@0 3314 options.errorMsg = MMI_ERROR_KS_BAD_PIN;
michael@0 3315 } else if (mmiServiceCode === MMI_KS_SC_PUK ||
michael@0 3316 mmiServiceCode === MMI_KS_SC_PUK2) {
michael@0 3317 options.errorMsg = MMI_ERROR_KS_BAD_PUK;
michael@0 3318 }
michael@0 3319 if (options.retryCount !== undefined) {
michael@0 3320 options.additionalInformation = options.retryCount;
michael@0 3321 }
michael@0 3322 }
michael@0 3323 }
michael@0 3324
michael@0 3325 this.sendChromeMessage(options);
michael@0 3326 },
michael@0 3327
michael@0 3328 // We combine all of the NETWORK_INFO_MESSAGE_TYPES into one "networkinfochange"
michael@0 3329 // message to the RadioInterfaceLayer, so we can avoid sending multiple
michael@0 3330 // VoiceInfoChanged events for both operator / voice_data_registration
michael@0 3331 //
michael@0 3332 // State management here is a little tricky. We need to know both:
michael@0 3333 // 1. Whether or not a response was received for each of the
michael@0 3334 // NETWORK_INFO_MESSAGE_TYPES
michael@0 3335 // 2. The outbound message that corresponds with that response -- but this
michael@0 3336 // only happens when internal state changes (i.e. it isn't guaranteed)
michael@0 3337 //
michael@0 3338 // To collect this state, each message response function first calls
michael@0 3339 // _receivedNetworkInfo, to mark the response as received. When the
michael@0 3340 // final response is received, a call to _sendPendingNetworkInfo is placed
michael@0 3341 // on the next tick of the worker thread.
michael@0 3342 //
michael@0 3343 // Since the original call to _receivedNetworkInfo happens at the top
michael@0 3344 // of the response handler, this gives the final handler a chance to
michael@0 3345 // queue up it's "changed" message by calling _sendNetworkInfoMessage if/when
michael@0 3346 // the internal state has actually changed.
michael@0 3347 _sendNetworkInfoMessage: function(type, message) {
michael@0 3348 if (!this._processingNetworkInfo) {
michael@0 3349 // We only combine these messages in the case of the combined request
michael@0 3350 // in requestNetworkInfo()
michael@0 3351 this.sendChromeMessage(message);
michael@0 3352 return;
michael@0 3353 }
michael@0 3354
michael@0 3355 if (DEBUG) {
michael@0 3356 this.context.debug("Queuing " + type + " network info message: " +
michael@0 3357 JSON.stringify(message));
michael@0 3358 }
michael@0 3359 this._pendingNetworkInfo[type] = message;
michael@0 3360 },
michael@0 3361
michael@0 3362 _receivedNetworkInfo: function(type) {
michael@0 3363 if (DEBUG) this.context.debug("Received " + type + " network info.");
michael@0 3364 if (!this._processingNetworkInfo) {
michael@0 3365 return;
michael@0 3366 }
michael@0 3367
michael@0 3368 let pending = this._pendingNetworkInfo;
michael@0 3369
michael@0 3370 // We still need to track states for events that aren't fired.
michael@0 3371 if (!(type in pending)) {
michael@0 3372 pending[type] = this.pendingNetworkType;
michael@0 3373 }
michael@0 3374
michael@0 3375 // Pending network info is ready to be sent when no more messages
michael@0 3376 // are waiting for responses, but the combined payload hasn't been sent.
michael@0 3377 for (let i = 0; i < NETWORK_INFO_MESSAGE_TYPES.length; i++) {
michael@0 3378 let msgType = NETWORK_INFO_MESSAGE_TYPES[i];
michael@0 3379 if (!(msgType in pending)) {
michael@0 3380 if (DEBUG) {
michael@0 3381 this.context.debug("Still missing some more network info, not " +
michael@0 3382 "notifying main thread.");
michael@0 3383 }
michael@0 3384 return;
michael@0 3385 }
michael@0 3386 }
michael@0 3387
michael@0 3388 // Do a pass to clean up the processed messages that didn't create
michael@0 3389 // a response message, so we don't have unused keys in the outbound
michael@0 3390 // networkinfochanged message.
michael@0 3391 for (let key in pending) {
michael@0 3392 if (pending[key] == this.pendingNetworkType) {
michael@0 3393 delete pending[key];
michael@0 3394 }
michael@0 3395 }
michael@0 3396
michael@0 3397 if (DEBUG) {
michael@0 3398 this.context.debug("All pending network info has been received: " +
michael@0 3399 JSON.stringify(pending));
michael@0 3400 }
michael@0 3401
michael@0 3402 // Send the message on the next tick of the worker's loop, so we give the
michael@0 3403 // last message a chance to call _sendNetworkInfoMessage first.
michael@0 3404 setTimeout(this._sendPendingNetworkInfo.bind(this), 0);
michael@0 3405 },
michael@0 3406
michael@0 3407 _sendPendingNetworkInfo: function() {
michael@0 3408 this.sendChromeMessage(this._pendingNetworkInfo);
michael@0 3409
michael@0 3410 this._processingNetworkInfo = false;
michael@0 3411 for (let i = 0; i < NETWORK_INFO_MESSAGE_TYPES.length; i++) {
michael@0 3412 delete this._pendingNetworkInfo[NETWORK_INFO_MESSAGE_TYPES[i]];
michael@0 3413 }
michael@0 3414
michael@0 3415 if (this._needRepollNetworkInfo) {
michael@0 3416 this._needRepollNetworkInfo = false;
michael@0 3417 this.requestNetworkInfo();
michael@0 3418 }
michael@0 3419 },
michael@0 3420
michael@0 3421 /**
michael@0 3422 * Normalize the signal strength in dBm to the signal level from 0 to 100.
michael@0 3423 *
michael@0 3424 * @param signal
michael@0 3425 * The signal strength in dBm to normalize.
michael@0 3426 * @param min
michael@0 3427 * The signal strength in dBm maps to level 0.
michael@0 3428 * @param max
michael@0 3429 * The signal strength in dBm maps to level 100.
michael@0 3430 *
michael@0 3431 * @return level
michael@0 3432 * The signal level from 0 to 100.
michael@0 3433 */
michael@0 3434 _processSignalLevel: function(signal, min, max) {
michael@0 3435 if (signal <= min) {
michael@0 3436 return 0;
michael@0 3437 }
michael@0 3438
michael@0 3439 if (signal >= max) {
michael@0 3440 return 100;
michael@0 3441 }
michael@0 3442
michael@0 3443 return Math.floor((signal - min) * 100 / (max - min));
michael@0 3444 },
michael@0 3445
michael@0 3446 /**
michael@0 3447 * Process LTE signal strength to the signal info object.
michael@0 3448 *
michael@0 3449 * @param signal
michael@0 3450 * The signal object reported from RIL/modem.
michael@0 3451 *
michael@0 3452 * @return The object of signal strength info.
michael@0 3453 * Or null if invalid signal input.
michael@0 3454 */
michael@0 3455 _processLteSignal: function(signal) {
michael@0 3456 // Valid values are 0-63 as defined in TS 27.007 clause 8.69.
michael@0 3457 if (signal.lteSignalStrength === undefined ||
michael@0 3458 signal.lteSignalStrength < 0 ||
michael@0 3459 signal.lteSignalStrength > 63) {
michael@0 3460 return null;
michael@0 3461 }
michael@0 3462
michael@0 3463 let info = {
michael@0 3464 voice: {
michael@0 3465 signalStrength: null,
michael@0 3466 relSignalStrength: null
michael@0 3467 },
michael@0 3468 data: {
michael@0 3469 signalStrength: null,
michael@0 3470 relSignalStrength: null
michael@0 3471 }
michael@0 3472 };
michael@0 3473
michael@0 3474 // TODO: Bug 982013: reconsider signalStrength/relSignalStrength APIs for
michael@0 3475 // GSM/CDMA/LTE, and take rsrp/rssnr into account for LTE case then.
michael@0 3476 let signalStrength = -111 + signal.lteSignalStrength;
michael@0 3477 info.voice.signalStrength = info.data.signalStrength = signalStrength;
michael@0 3478 // 0 and 12 are referred to AOSP's implementation. These values are not
michael@0 3479 // constants and can be customized based on different requirements.
michael@0 3480 let signalLevel = this._processSignalLevel(signal.lteSignalStrength, 0, 12);
michael@0 3481 info.voice.relSignalStrength = info.data.relSignalStrength = signalLevel;
michael@0 3482
michael@0 3483 return info;
michael@0 3484 },
michael@0 3485
michael@0 3486 _processSignalStrength: function(signal) {
michael@0 3487 let info = {
michael@0 3488 voice: {
michael@0 3489 signalStrength: null,
michael@0 3490 relSignalStrength: null
michael@0 3491 },
michael@0 3492 data: {
michael@0 3493 signalStrength: null,
michael@0 3494 relSignalStrength: null
michael@0 3495 }
michael@0 3496 };
michael@0 3497
michael@0 3498 // During startup, |radioTech| is not yet defined, so we need to
michael@0 3499 // check it separately.
michael@0 3500 if (("radioTech" in this.voiceRegistrationState) &&
michael@0 3501 !this._isGsmTechGroup(this.voiceRegistrationState.radioTech)) {
michael@0 3502 // CDMA RSSI.
michael@0 3503 // Valid values are positive integers. This value is the actual RSSI value
michael@0 3504 // multiplied by -1. Example: If the actual RSSI is -75, then this
michael@0 3505 // response value will be 75.
michael@0 3506 if (signal.cdmaDBM && signal.cdmaDBM > 0) {
michael@0 3507 let signalStrength = -1 * signal.cdmaDBM;
michael@0 3508 info.voice.signalStrength = signalStrength;
michael@0 3509
michael@0 3510 // -105 and -70 are referred to AOSP's implementation. These values are
michael@0 3511 // not constants and can be customized based on different requirement.
michael@0 3512 let signalLevel = this._processSignalLevel(signalStrength, -105, -70);
michael@0 3513 info.voice.relSignalStrength = signalLevel;
michael@0 3514 }
michael@0 3515
michael@0 3516 // EVDO RSSI.
michael@0 3517 // Valid values are positive integers. This value is the actual RSSI value
michael@0 3518 // multiplied by -1. Example: If the actual RSSI is -75, then this
michael@0 3519 // response value will be 75.
michael@0 3520 if (signal.evdoDBM && signal.evdoDBM > 0) {
michael@0 3521 let signalStrength = -1 * signal.evdoDBM;
michael@0 3522 info.data.signalStrength = signalStrength;
michael@0 3523
michael@0 3524 // -105 and -70 are referred to AOSP's implementation. These values are
michael@0 3525 // not constants and can be customized based on different requirement.
michael@0 3526 let signalLevel = this._processSignalLevel(signalStrength, -105, -70);
michael@0 3527 info.data.relSignalStrength = signalLevel;
michael@0 3528 }
michael@0 3529 } else {
michael@0 3530 // Check LTE level first, and check GSM/UMTS level next if LTE one is not
michael@0 3531 // valid.
michael@0 3532 let lteInfo = this._processLteSignal(signal);
michael@0 3533 if (lteInfo) {
michael@0 3534 info = lteInfo;
michael@0 3535 } else {
michael@0 3536 // GSM signal strength.
michael@0 3537 // Valid values are 0-31 as defined in TS 27.007 8.5.
michael@0 3538 // 0 : -113 dBm or less
michael@0 3539 // 1 : -111 dBm
michael@0 3540 // 2...30: -109...-53 dBm
michael@0 3541 // 31 : -51 dBm
michael@0 3542 if (signal.gsmSignalStrength &&
michael@0 3543 signal.gsmSignalStrength >= 0 &&
michael@0 3544 signal.gsmSignalStrength <= 31) {
michael@0 3545 let signalStrength = -113 + 2 * signal.gsmSignalStrength;
michael@0 3546 info.voice.signalStrength = info.data.signalStrength = signalStrength;
michael@0 3547
michael@0 3548 // -115 and -85 are referred to AOSP's implementation. These values are
michael@0 3549 // not constants and can be customized based on different requirement.
michael@0 3550 let signalLevel = this._processSignalLevel(signalStrength, -110, -85);
michael@0 3551 info.voice.relSignalStrength = info.data.relSignalStrength = signalLevel;
michael@0 3552 }
michael@0 3553 }
michael@0 3554 }
michael@0 3555
michael@0 3556 info.rilMessageType = "signalstrengthchange";
michael@0 3557 this._sendNetworkInfoMessage(NETWORK_INFO_SIGNAL, info);
michael@0 3558
michael@0 3559 if (this.cachedDialRequest && info.voice.signalStrength) {
michael@0 3560 // Radio is ready for making the cached emergency call.
michael@0 3561 this.cachedDialRequest.callback();
michael@0 3562 this.cachedDialRequest = null;
michael@0 3563 }
michael@0 3564 },
michael@0 3565
michael@0 3566 /**
michael@0 3567 * Process the network registration flags.
michael@0 3568 *
michael@0 3569 * @return true if the state changed, false otherwise.
michael@0 3570 */
michael@0 3571 _processCREG: function(curState, newState) {
michael@0 3572 let changed = false;
michael@0 3573
michael@0 3574 let regState = this.parseInt(newState[0], NETWORK_CREG_STATE_UNKNOWN);
michael@0 3575 if (curState.regState === undefined || curState.regState !== regState) {
michael@0 3576 changed = true;
michael@0 3577 curState.regState = regState;
michael@0 3578
michael@0 3579 curState.state = NETWORK_CREG_TO_GECKO_MOBILE_CONNECTION_STATE[regState];
michael@0 3580 curState.connected = regState == NETWORK_CREG_STATE_REGISTERED_HOME ||
michael@0 3581 regState == NETWORK_CREG_STATE_REGISTERED_ROAMING;
michael@0 3582 curState.roaming = regState == NETWORK_CREG_STATE_REGISTERED_ROAMING;
michael@0 3583 curState.emergencyCallsOnly = !curState.connected;
michael@0 3584 }
michael@0 3585
michael@0 3586 if (!curState.cell) {
michael@0 3587 curState.cell = {};
michael@0 3588 }
michael@0 3589
michael@0 3590 // From TS 23.003, 0000 and 0xfffe are indicated that no valid LAI exists
michael@0 3591 // in MS. So we still need to report the '0000' as well.
michael@0 3592 let lac = this.parseInt(newState[1], -1, 16);
michael@0 3593 if (curState.cell.gsmLocationAreaCode === undefined ||
michael@0 3594 curState.cell.gsmLocationAreaCode !== lac) {
michael@0 3595 curState.cell.gsmLocationAreaCode = lac;
michael@0 3596 changed = true;
michael@0 3597 }
michael@0 3598
michael@0 3599 let cid = this.parseInt(newState[2], -1, 16);
michael@0 3600 if (curState.cell.gsmCellId === undefined ||
michael@0 3601 curState.cell.gsmCellId !== cid) {
michael@0 3602 curState.cell.gsmCellId = cid;
michael@0 3603 changed = true;
michael@0 3604 }
michael@0 3605
michael@0 3606 let radioTech = (newState[3] === undefined ?
michael@0 3607 NETWORK_CREG_TECH_UNKNOWN :
michael@0 3608 this.parseInt(newState[3], NETWORK_CREG_TECH_UNKNOWN));
michael@0 3609 if (curState.radioTech === undefined || curState.radioTech !== radioTech) {
michael@0 3610 changed = true;
michael@0 3611 curState.radioTech = radioTech;
michael@0 3612 curState.type = GECKO_RADIO_TECH[radioTech] || null;
michael@0 3613 }
michael@0 3614 return changed;
michael@0 3615 },
michael@0 3616
michael@0 3617 _processVoiceRegistrationState: function(state) {
michael@0 3618 let rs = this.voiceRegistrationState;
michael@0 3619 let stateChanged = this._processCREG(rs, state);
michael@0 3620 if (stateChanged && rs.connected) {
michael@0 3621 this.getSmscAddress();
michael@0 3622 }
michael@0 3623
michael@0 3624 let cell = rs.cell;
michael@0 3625 if (this._isCdma) {
michael@0 3626 // Some variables below are not used. Comment them instead of removing to
michael@0 3627 // keep the information about state[x].
michael@0 3628 let cdmaBaseStationId = this.parseInt(state[4], -1);
michael@0 3629 let cdmaBaseStationLatitude = this.parseInt(state[5], -2147483648);
michael@0 3630 let cdmaBaseStationLongitude = this.parseInt(state[6], -2147483648);
michael@0 3631 // let cssIndicator = this.parseInt(state[7]);
michael@0 3632 let cdmaSystemId = this.parseInt(state[8], -1);
michael@0 3633 let cdmaNetworkId = this.parseInt(state[9], -1);
michael@0 3634 // let roamingIndicator = this.parseInt(state[10]);
michael@0 3635 // let systemIsInPRL = this.parseInt(state[11]);
michael@0 3636 // let defaultRoamingIndicator = this.parseInt(state[12]);
michael@0 3637 // let reasonForDenial = this.parseInt(state[13]);
michael@0 3638
michael@0 3639 if (cell.cdmaBaseStationId !== cdmaBaseStationId ||
michael@0 3640 cell.cdmaBaseStationLatitude !== cdmaBaseStationLatitude ||
michael@0 3641 cell.cdmaBaseStationLongitude !== cdmaBaseStationLongitude ||
michael@0 3642 cell.cdmaSystemId !== cdmaSystemId ||
michael@0 3643 cell.cdmaNetworkId !== cdmaNetworkId) {
michael@0 3644 stateChanged = true;
michael@0 3645 cell.cdmaBaseStationId = cdmaBaseStationId;
michael@0 3646 cell.cdmaBaseStationLatitude = cdmaBaseStationLatitude;
michael@0 3647 cell.cdmaBaseStationLongitude = cdmaBaseStationLongitude;
michael@0 3648 cell.cdmaSystemId = cdmaSystemId;
michael@0 3649 cell.cdmaNetworkId = cdmaNetworkId;
michael@0 3650 }
michael@0 3651 }
michael@0 3652
michael@0 3653 if (stateChanged) {
michael@0 3654 rs.rilMessageType = "voiceregistrationstatechange";
michael@0 3655 this._sendNetworkInfoMessage(NETWORK_INFO_VOICE_REGISTRATION_STATE, rs);
michael@0 3656 }
michael@0 3657 },
michael@0 3658
michael@0 3659 _processDataRegistrationState: function(state) {
michael@0 3660 let rs = this.dataRegistrationState;
michael@0 3661 let stateChanged = this._processCREG(rs, state);
michael@0 3662 if (stateChanged) {
michael@0 3663 rs.rilMessageType = "dataregistrationstatechange";
michael@0 3664 this._sendNetworkInfoMessage(NETWORK_INFO_DATA_REGISTRATION_STATE, rs);
michael@0 3665 }
michael@0 3666 },
michael@0 3667
michael@0 3668 _processOperator: function(operatorData) {
michael@0 3669 if (operatorData.length < 3) {
michael@0 3670 if (DEBUG) {
michael@0 3671 this.context.debug("Expected at least 3 strings for operator.");
michael@0 3672 }
michael@0 3673 }
michael@0 3674
michael@0 3675 if (!this.operator) {
michael@0 3676 this.operator = {
michael@0 3677 rilMessageType: "operatorchange",
michael@0 3678 longName: null,
michael@0 3679 shortName: null
michael@0 3680 };
michael@0 3681 }
michael@0 3682
michael@0 3683 let [longName, shortName, networkTuple] = operatorData;
michael@0 3684 let thisTuple = (this.operator.mcc || "") + (this.operator.mnc || "");
michael@0 3685
michael@0 3686 if (this.operator.longName !== longName ||
michael@0 3687 this.operator.shortName !== shortName ||
michael@0 3688 thisTuple !== networkTuple) {
michael@0 3689
michael@0 3690 this.operator.mcc = null;
michael@0 3691 this.operator.mnc = null;
michael@0 3692
michael@0 3693 if (networkTuple) {
michael@0 3694 try {
michael@0 3695 this._processNetworkTuple(networkTuple, this.operator);
michael@0 3696 } catch (e) {
michael@0 3697 if (DEBUG) this.context.debug("Error processing operator tuple: " + e);
michael@0 3698 }
michael@0 3699 } else {
michael@0 3700 // According to ril.h, the operator fields will be NULL when the operator
michael@0 3701 // is not currently registered. We can avoid trying to parse the numeric
michael@0 3702 // tuple in that case.
michael@0 3703 if (DEBUG) {
michael@0 3704 this.context.debug("Operator is currently unregistered");
michael@0 3705 }
michael@0 3706 }
michael@0 3707
michael@0 3708 let ICCUtilsHelper = this.context.ICCUtilsHelper;
michael@0 3709 let networkName;
michael@0 3710 // We won't get network name using PNN and OPL if voice registration isn't ready
michael@0 3711 if (this.voiceRegistrationState.cell &&
michael@0 3712 this.voiceRegistrationState.cell.gsmLocationAreaCode != -1) {
michael@0 3713 networkName = ICCUtilsHelper.getNetworkNameFromICC(
michael@0 3714 this.operator.mcc,
michael@0 3715 this.operator.mnc,
michael@0 3716 this.voiceRegistrationState.cell.gsmLocationAreaCode);
michael@0 3717 }
michael@0 3718
michael@0 3719 if (networkName) {
michael@0 3720 if (DEBUG) {
michael@0 3721 this.context.debug("Operator names will be overriden: " +
michael@0 3722 "longName = " + networkName.fullName + ", " +
michael@0 3723 "shortName = " + networkName.shortName);
michael@0 3724 }
michael@0 3725
michael@0 3726 this.operator.longName = networkName.fullName;
michael@0 3727 this.operator.shortName = networkName.shortName;
michael@0 3728 } else {
michael@0 3729 this.operator.longName = longName;
michael@0 3730 this.operator.shortName = shortName;
michael@0 3731 }
michael@0 3732
michael@0 3733 if (ICCUtilsHelper.updateDisplayCondition()) {
michael@0 3734 ICCUtilsHelper.handleICCInfoChange();
michael@0 3735 }
michael@0 3736 this._sendNetworkInfoMessage(NETWORK_INFO_OPERATOR, this.operator);
michael@0 3737 }
michael@0 3738 },
michael@0 3739
michael@0 3740 /**
michael@0 3741 * Helpers for processing call state and handle the active call.
michael@0 3742 */
michael@0 3743 _processCalls: function(newCalls) {
michael@0 3744 let conferenceChanged = false;
michael@0 3745 let clearConferenceRequest = false;
michael@0 3746 let pendingOutgoingCall = null;
michael@0 3747
michael@0 3748 // Go through the calls we currently have on file and see if any of them
michael@0 3749 // changed state. Remove them from the newCalls map as we deal with them
michael@0 3750 // so that only new calls remain in the map after we're done.
michael@0 3751 for each (let currentCall in this.currentCalls) {
michael@0 3752 if (currentCall.callIndex == OUTGOING_PLACEHOLDER_CALL_INDEX) {
michael@0 3753 pendingOutgoingCall = currentCall;
michael@0 3754 continue;
michael@0 3755 }
michael@0 3756
michael@0 3757 let newCall;
michael@0 3758 if (newCalls) {
michael@0 3759 newCall = newCalls[currentCall.callIndex];
michael@0 3760 delete newCalls[currentCall.callIndex];
michael@0 3761 }
michael@0 3762
michael@0 3763 // Call is no longer reported by the radio. Remove from our map and send
michael@0 3764 // disconnected state change.
michael@0 3765 if (!newCall) {
michael@0 3766 if (this.currentConference.participants[currentCall.callIndex]) {
michael@0 3767 conferenceChanged = true;
michael@0 3768 }
michael@0 3769 this._removeVoiceCall(currentCall,
michael@0 3770 currentCall.hangUpLocal ?
michael@0 3771 GECKO_CALL_ERROR_NORMAL_CALL_CLEARING : null);
michael@0 3772 continue;
michael@0 3773 }
michael@0 3774
michael@0 3775 // Call is still valid.
michael@0 3776 if (newCall.state == currentCall.state &&
michael@0 3777 newCall.isMpty == currentCall.isMpty) {
michael@0 3778 continue;
michael@0 3779 }
michael@0 3780
michael@0 3781 // State has changed.
michael@0 3782 if (newCall.state == CALL_STATE_INCOMING &&
michael@0 3783 currentCall.state == CALL_STATE_WAITING) {
michael@0 3784 // Update the call internally but we don't notify chrome since these two
michael@0 3785 // states are viewed as the same one there.
michael@0 3786 currentCall.state = newCall.state;
michael@0 3787 continue;
michael@0 3788 }
michael@0 3789
michael@0 3790 if (!currentCall.started && newCall.state == CALL_STATE_ACTIVE) {
michael@0 3791 currentCall.started = new Date().getTime();
michael@0 3792 }
michael@0 3793
michael@0 3794 if (currentCall.isMpty == newCall.isMpty &&
michael@0 3795 newCall.state != currentCall.state) {
michael@0 3796 currentCall.state = newCall.state;
michael@0 3797 if (currentCall.isConference) {
michael@0 3798 conferenceChanged = true;
michael@0 3799 }
michael@0 3800 this._handleChangedCallState(currentCall);
michael@0 3801 continue;
michael@0 3802 }
michael@0 3803
michael@0 3804 // '.isMpty' becomes false when the conference call is put on hold.
michael@0 3805 // We need to introduce additional 'isConference' to correctly record the
michael@0 3806 // real conference status
michael@0 3807
michael@0 3808 // Update a possible conference participant when .isMpty changes.
michael@0 3809 if (!currentCall.isMpty && newCall.isMpty) {
michael@0 3810 if (this._hasConferenceRequest) {
michael@0 3811 conferenceChanged = true;
michael@0 3812 clearConferenceRequest = true;
michael@0 3813 currentCall.state = newCall.state;
michael@0 3814 currentCall.isMpty = newCall.isMpty;
michael@0 3815 currentCall.isConference = true;
michael@0 3816 this.currentConference.participants[currentCall.callIndex] = currentCall;
michael@0 3817 this._handleChangedCallState(currentCall);
michael@0 3818 } else if (currentCall.isConference) {
michael@0 3819 // The case happens when resuming a held conference call.
michael@0 3820 conferenceChanged = true;
michael@0 3821 currentCall.state = newCall.state;
michael@0 3822 currentCall.isMpty = newCall.isMpty;
michael@0 3823 this.currentConference.participants[currentCall.callIndex] = currentCall;
michael@0 3824 this._handleChangedCallState(currentCall);
michael@0 3825 } else {
michael@0 3826 // Weird. This sometimes happens when we switch two calls, but it is
michael@0 3827 // not a conference call.
michael@0 3828 currentCall.state = newCall.state;
michael@0 3829 this._handleChangedCallState(currentCall);
michael@0 3830 }
michael@0 3831 } else if (currentCall.isMpty && !newCall.isMpty) {
michael@0 3832 if (!this.currentConference.participants[newCall.callIndex]) {
michael@0 3833 continue;
michael@0 3834 }
michael@0 3835
michael@0 3836 // '.isMpty' of a conference participant is set to false by rild when
michael@0 3837 // the conference call is put on hold. We don't actually know if the call
michael@0 3838 // still attends the conference until updating all calls finishes. We
michael@0 3839 // cache it for further determination.
michael@0 3840 if (newCall.state != CALL_STATE_HOLDING) {
michael@0 3841 delete this.currentConference.participants[newCall.callIndex];
michael@0 3842 currentCall.state = newCall.state;
michael@0 3843 currentCall.isMpty = newCall.isMpty;
michael@0 3844 currentCall.isConference = false;
michael@0 3845 conferenceChanged = true;
michael@0 3846 this._handleChangedCallState(currentCall);
michael@0 3847 continue;
michael@0 3848 }
michael@0 3849
michael@0 3850 if (!this.currentConference.cache) {
michael@0 3851 this.currentConference.cache = {};
michael@0 3852 }
michael@0 3853 this.currentConference.cache[currentCall.callIndex] = newCall;
michael@0 3854 currentCall.state = newCall.state;
michael@0 3855 currentCall.isMpty = newCall.isMpty;
michael@0 3856 conferenceChanged = true;
michael@0 3857 }
michael@0 3858 }
michael@0 3859
michael@0 3860 if (pendingOutgoingCall) {
michael@0 3861 if (!newCalls || Object.keys(newCalls).length === 0) {
michael@0 3862 // We don't get a successful call for pendingOutgoingCall.
michael@0 3863 this._removePendingOutgoingCall(GECKO_CALL_ERROR_UNSPECIFIED);
michael@0 3864 } else {
michael@0 3865 // Only remove it from currentCalls map. Will use the new call to
michael@0 3866 // replace the placeholder.
michael@0 3867 delete this.currentCalls[OUTGOING_PLACEHOLDER_CALL_INDEX];
michael@0 3868 }
michael@0 3869 }
michael@0 3870
michael@0 3871 // Go through any remaining calls that are new to us.
michael@0 3872 for each (let newCall in newCalls) {
michael@0 3873 if (newCall.isVoice) {
michael@0 3874 if (newCall.isMpty) {
michael@0 3875 conferenceChanged = true;
michael@0 3876 }
michael@0 3877 if (!pendingOutgoingCall &&
michael@0 3878 (newCall.state === CALL_STATE_DIALING ||
michael@0 3879 newCall.state === CALL_STATE_ALERTING)) {
michael@0 3880 // Receive a new outgoing call which is already hung up by user.
michael@0 3881 if (DEBUG) this.context.debug("Pending outgoing call is hung up by user.");
michael@0 3882 this.sendHangUpRequest(newCall.callIndex);
michael@0 3883 } else {
michael@0 3884 this._addNewVoiceCall(newCall);
michael@0 3885 }
michael@0 3886 }
michael@0 3887 }
michael@0 3888
michael@0 3889 if (clearConferenceRequest) {
michael@0 3890 this._hasConferenceRequest = false;
michael@0 3891 }
michael@0 3892 if (conferenceChanged) {
michael@0 3893 this._ensureConference();
michael@0 3894 }
michael@0 3895 },
michael@0 3896
michael@0 3897 _addNewVoiceCall: function(newCall) {
michael@0 3898 // Format international numbers appropriately.
michael@0 3899 if (newCall.number && newCall.toa == TOA_INTERNATIONAL &&
michael@0 3900 newCall.number[0] != "+") {
michael@0 3901 newCall.number = "+" + newCall.number;
michael@0 3902 }
michael@0 3903
michael@0 3904 if (newCall.state == CALL_STATE_INCOMING) {
michael@0 3905 newCall.isOutgoing = false;
michael@0 3906 } else if (newCall.state == CALL_STATE_DIALING) {
michael@0 3907 newCall.isOutgoing = true;
michael@0 3908 }
michael@0 3909
michael@0 3910 // Set flag for outgoing emergency call.
michael@0 3911 newCall.isEmergency = newCall.isOutgoing &&
michael@0 3912 this._isEmergencyNumber(newCall.number);
michael@0 3913
michael@0 3914 // Set flag for conference.
michael@0 3915 newCall.isConference = newCall.isMpty ? true : false;
michael@0 3916
michael@0 3917 // Add to our map.
michael@0 3918 if (newCall.isMpty) {
michael@0 3919 this.currentConference.participants[newCall.callIndex] = newCall;
michael@0 3920 }
michael@0 3921 this._handleChangedCallState(newCall);
michael@0 3922 this.currentCalls[newCall.callIndex] = newCall;
michael@0 3923 },
michael@0 3924
michael@0 3925 _removeVoiceCall: function(removedCall, failCause) {
michael@0 3926 if (this.currentConference.participants[removedCall.callIndex]) {
michael@0 3927 removedCall.isConference = false;
michael@0 3928 delete this.currentConference.participants[removedCall.callIndex];
michael@0 3929 delete this.currentCalls[removedCall.callIndex];
michael@0 3930 // We don't query the fail cause here as it triggers another asynchrouns
michael@0 3931 // request that leads to a problem of updating all conferece participants
michael@0 3932 // in one task.
michael@0 3933 this._handleDisconnectedCall(removedCall);
michael@0 3934 } else {
michael@0 3935 delete this.currentCalls[removedCall.callIndex];
michael@0 3936 if (failCause) {
michael@0 3937 removedCall.failCause = failCause;
michael@0 3938 this._handleDisconnectedCall(removedCall);
michael@0 3939 } else {
michael@0 3940 this.getFailCauseCode((function(call, failCause) {
michael@0 3941 call.failCause = failCause;
michael@0 3942 this._handleDisconnectedCall(call);
michael@0 3943 }).bind(this, removedCall));
michael@0 3944 }
michael@0 3945 }
michael@0 3946 },
michael@0 3947
michael@0 3948 _createPendingOutgoingCall: function(options) {
michael@0 3949 if (DEBUG) this.context.debug("Create a pending outgoing call.");
michael@0 3950 this._addNewVoiceCall({
michael@0 3951 number: options.number,
michael@0 3952 state: CALL_STATE_DIALING,
michael@0 3953 callIndex: OUTGOING_PLACEHOLDER_CALL_INDEX
michael@0 3954 });
michael@0 3955 },
michael@0 3956
michael@0 3957 _removePendingOutgoingCall: function(failCause) {
michael@0 3958 let call = this.currentCalls[OUTGOING_PLACEHOLDER_CALL_INDEX];
michael@0 3959 if (!call) {
michael@0 3960 return;
michael@0 3961 }
michael@0 3962
michael@0 3963 if (DEBUG) this.context.debug("Remove pending outgoing call.");
michael@0 3964 this._removeVoiceCall(pendingOutgoingCall, failCause);
michael@0 3965 },
michael@0 3966
michael@0 3967 _ensureConference: function() {
michael@0 3968 let oldState = this.currentConference.state;
michael@0 3969 let remaining = Object.keys(this.currentConference.participants);
michael@0 3970
michael@0 3971 if (remaining.length == 1) {
michael@0 3972 // Remove that if only does one remain in a conference call.
michael@0 3973 let call = this.currentCalls[remaining[0]];
michael@0 3974 call.isConference = false;
michael@0 3975 this._handleChangedCallState(call);
michael@0 3976 delete this.currentConference.participants[call.callIndex];
michael@0 3977 } else if (remaining.length > 1) {
michael@0 3978 for each (let call in this.currentConference.cache) {
michael@0 3979 call.isConference = true;
michael@0 3980 this.currentConference.participants[call.callIndex] = call;
michael@0 3981 this.currentCalls[call.callIndex] = call;
michael@0 3982 this._handleChangedCallState(call);
michael@0 3983 }
michael@0 3984 }
michael@0 3985 delete this.currentConference.cache;
michael@0 3986
michael@0 3987 // Update the conference call's state.
michael@0 3988 let state = CALL_STATE_UNKNOWN;
michael@0 3989 for each (let call in this.currentConference.participants) {
michael@0 3990 if (state != CALL_STATE_UNKNOWN && state != call.state) {
michael@0 3991 // Each participant should have the same state, otherwise something
michael@0 3992 // wrong happens.
michael@0 3993 state = CALL_STATE_UNKNOWN;
michael@0 3994 break;
michael@0 3995 }
michael@0 3996 state = call.state;
michael@0 3997 }
michael@0 3998 if (oldState != state) {
michael@0 3999 this.currentConference.state = state;
michael@0 4000 let message = {rilMessageType: "conferenceCallStateChanged",
michael@0 4001 state: state};
michael@0 4002 this.sendChromeMessage(message);
michael@0 4003 }
michael@0 4004 },
michael@0 4005
michael@0 4006 _handleChangedCallState: function(changedCall) {
michael@0 4007 let message = {rilMessageType: "callStateChange",
michael@0 4008 call: changedCall};
michael@0 4009 this.sendChromeMessage(message);
michael@0 4010 },
michael@0 4011
michael@0 4012 _handleDisconnectedCall: function(disconnectedCall) {
michael@0 4013 let message = {rilMessageType: "callDisconnected",
michael@0 4014 call: disconnectedCall};
michael@0 4015 this.sendChromeMessage(message);
michael@0 4016 },
michael@0 4017
michael@0 4018 _sendDataCallError: function(message, errorCode) {
michael@0 4019 // Should not include token for unsolicited response.
michael@0 4020 delete message.rilMessageToken;
michael@0 4021 message.rilMessageType = "datacallerror";
michael@0 4022 if (errorCode == ERROR_GENERIC_FAILURE) {
michael@0 4023 message.errorMsg = RIL_ERROR_TO_GECKO_ERROR[errorCode];
michael@0 4024 } else {
michael@0 4025 message.errorMsg = RIL_DATACALL_FAILCAUSE_TO_GECKO_DATACALL_ERROR[errorCode];
michael@0 4026 }
michael@0 4027 this.sendChromeMessage(message);
michael@0 4028 },
michael@0 4029
michael@0 4030 /**
michael@0 4031 * @return "deactivate" if <ifname> changes or one of the currentDataCall
michael@0 4032 * addresses is missing in updatedDataCall, or "identical" if no
michael@0 4033 * changes found, or "changed" otherwise.
michael@0 4034 */
michael@0 4035 _compareDataCallLink: function(updatedDataCall, currentDataCall) {
michael@0 4036 // If network interface is changed, report as "deactivate".
michael@0 4037 if (updatedDataCall.ifname != currentDataCall.ifname) {
michael@0 4038 return "deactivate";
michael@0 4039 }
michael@0 4040
michael@0 4041 // If any existing address is missing, report as "deactivate".
michael@0 4042 for (let i = 0; i < currentDataCall.addresses.length; i++) {
michael@0 4043 let address = currentDataCall.addresses[i];
michael@0 4044 if (updatedDataCall.addresses.indexOf(address) < 0) {
michael@0 4045 return "deactivate";
michael@0 4046 }
michael@0 4047 }
michael@0 4048
michael@0 4049 if (currentDataCall.addresses.length != updatedDataCall.addresses.length) {
michael@0 4050 // Since now all |currentDataCall.addresses| are found in
michael@0 4051 // |updatedDataCall.addresses|, this means one or more new addresses are
michael@0 4052 // reported.
michael@0 4053 return "changed";
michael@0 4054 }
michael@0 4055
michael@0 4056 let fields = ["gateways", "dnses"];
michael@0 4057 for (let i = 0; i < fields.length; i++) {
michael@0 4058 // Compare <datacall>.<field>.
michael@0 4059 let field = fields[i];
michael@0 4060 let lhs = updatedDataCall[field], rhs = currentDataCall[field];
michael@0 4061 if (lhs.length != rhs.length) {
michael@0 4062 return "changed";
michael@0 4063 }
michael@0 4064 for (let i = 0; i < lhs.length; i++) {
michael@0 4065 if (lhs[i] != rhs[i]) {
michael@0 4066 return "changed";
michael@0 4067 }
michael@0 4068 }
michael@0 4069 }
michael@0 4070
michael@0 4071 return "identical";
michael@0 4072 },
michael@0 4073
michael@0 4074 _processDataCallList: function(datacalls, newDataCallOptions) {
michael@0 4075 // Check for possible PDP errors: We check earlier because the datacall
michael@0 4076 // can be removed if is the same as the current one.
michael@0 4077 for each (let newDataCall in datacalls) {
michael@0 4078 if (newDataCall.status != DATACALL_FAIL_NONE) {
michael@0 4079 if (newDataCallOptions) {
michael@0 4080 newDataCall.apn = newDataCallOptions.apn;
michael@0 4081 }
michael@0 4082 this._sendDataCallError(newDataCall, newDataCall.status);
michael@0 4083 }
michael@0 4084 }
michael@0 4085
michael@0 4086 for each (let currentDataCall in this.currentDataCalls) {
michael@0 4087 let updatedDataCall;
michael@0 4088 if (datacalls) {
michael@0 4089 updatedDataCall = datacalls[currentDataCall.cid];
michael@0 4090 delete datacalls[currentDataCall.cid];
michael@0 4091 }
michael@0 4092
michael@0 4093 if (!updatedDataCall) {
michael@0 4094 // If datacalls list is coming from REQUEST_SETUP_DATA_CALL response,
michael@0 4095 // we do not change state for any currentDataCalls not in datacalls list.
michael@0 4096 if (!newDataCallOptions) {
michael@0 4097 delete this.currentDataCalls[currentDataCall.cid];
michael@0 4098 currentDataCall.state = GECKO_NETWORK_STATE_DISCONNECTED;
michael@0 4099 currentDataCall.rilMessageType = "datacallstatechange";
michael@0 4100 this.sendChromeMessage(currentDataCall);
michael@0 4101 }
michael@0 4102 continue;
michael@0 4103 }
michael@0 4104
michael@0 4105 if (updatedDataCall && !updatedDataCall.ifname) {
michael@0 4106 delete this.currentDataCalls[currentDataCall.cid];
michael@0 4107 currentDataCall.state = GECKO_NETWORK_STATE_UNKNOWN;
michael@0 4108 currentDataCall.rilMessageType = "datacallstatechange";
michael@0 4109 this.sendChromeMessage(currentDataCall);
michael@0 4110 continue;
michael@0 4111 }
michael@0 4112
michael@0 4113 this._setDataCallGeckoState(updatedDataCall);
michael@0 4114 if (updatedDataCall.state != currentDataCall.state) {
michael@0 4115 if (updatedDataCall.state == GECKO_NETWORK_STATE_DISCONNECTED) {
michael@0 4116 delete this.currentDataCalls[currentDataCall.cid];
michael@0 4117 }
michael@0 4118 currentDataCall.status = updatedDataCall.status;
michael@0 4119 currentDataCall.active = updatedDataCall.active;
michael@0 4120 currentDataCall.state = updatedDataCall.state;
michael@0 4121 currentDataCall.rilMessageType = "datacallstatechange";
michael@0 4122 this.sendChromeMessage(currentDataCall);
michael@0 4123 continue;
michael@0 4124 }
michael@0 4125
michael@0 4126 // State not changed, now check links.
michael@0 4127 let result =
michael@0 4128 this._compareDataCallLink(updatedDataCall, currentDataCall);
michael@0 4129 if (result == "identical") {
michael@0 4130 if (DEBUG) this.context.debug("No changes in data call.");
michael@0 4131 continue;
michael@0 4132 }
michael@0 4133 if (result == "deactivate") {
michael@0 4134 if (DEBUG) this.context.debug("Data link changed, cleanup.");
michael@0 4135 this.deactivateDataCall(currentDataCall);
michael@0 4136 continue;
michael@0 4137 }
michael@0 4138 // Minor change, just update and notify.
michael@0 4139 if (DEBUG) {
michael@0 4140 this.context.debug("Data link minor change, just update and notify.");
michael@0 4141 }
michael@0 4142 currentDataCall.addresses = updatedDataCall.addresses.slice();
michael@0 4143 currentDataCall.dnses = updatedDataCall.dnses.slice();
michael@0 4144 currentDataCall.gateways = updatedDataCall.gateways.slice();
michael@0 4145 currentDataCall.rilMessageType = "datacallstatechange";
michael@0 4146 this.sendChromeMessage(currentDataCall);
michael@0 4147 }
michael@0 4148
michael@0 4149 for each (let newDataCall in datacalls) {
michael@0 4150 if (!newDataCall.ifname) {
michael@0 4151 continue;
michael@0 4152 }
michael@0 4153
michael@0 4154 if (!newDataCallOptions) {
michael@0 4155 if (DEBUG) {
michael@0 4156 this.context.debug("Unexpected new data call: " +
michael@0 4157 JSON.stringify(newDataCall));
michael@0 4158 }
michael@0 4159 continue;
michael@0 4160 }
michael@0 4161
michael@0 4162 this.currentDataCalls[newDataCall.cid] = newDataCall;
michael@0 4163 this._setDataCallGeckoState(newDataCall);
michael@0 4164
michael@0 4165 newDataCall.radioTech = newDataCallOptions.radioTech;
michael@0 4166 newDataCall.apn = newDataCallOptions.apn;
michael@0 4167 newDataCall.user = newDataCallOptions.user;
michael@0 4168 newDataCall.passwd = newDataCallOptions.passwd;
michael@0 4169 newDataCall.chappap = newDataCallOptions.chappap;
michael@0 4170 newDataCall.pdptype = newDataCallOptions.pdptype;
michael@0 4171 newDataCallOptions = null;
michael@0 4172
michael@0 4173 newDataCall.rilMessageType = "datacallstatechange";
michael@0 4174 this.sendChromeMessage(newDataCall);
michael@0 4175 }
michael@0 4176 },
michael@0 4177
michael@0 4178 _setDataCallGeckoState: function(datacall) {
michael@0 4179 switch (datacall.active) {
michael@0 4180 case DATACALL_INACTIVE:
michael@0 4181 datacall.state = GECKO_NETWORK_STATE_DISCONNECTED;
michael@0 4182 break;
michael@0 4183 case DATACALL_ACTIVE_DOWN:
michael@0 4184 case DATACALL_ACTIVE_UP:
michael@0 4185 datacall.state = GECKO_NETWORK_STATE_CONNECTED;
michael@0 4186 break;
michael@0 4187 }
michael@0 4188 },
michael@0 4189
michael@0 4190 _processSuppSvcNotification: function(info) {
michael@0 4191 if (DEBUG) {
michael@0 4192 this.context.debug("handle supp svc notification: " + JSON.stringify(info));
michael@0 4193 }
michael@0 4194
michael@0 4195 if (info.notificationType !== 1) {
michael@0 4196 // We haven't supported MO intermediate result code, i.e.
michael@0 4197 // info.notificationType === 0, which refers to code1 defined in 3GPP
michael@0 4198 // 27.007 7.17. We only support partial MT unsolicited result code,
michael@0 4199 // referring to code2, for now.
michael@0 4200 return;
michael@0 4201 }
michael@0 4202
michael@0 4203 let notification = null;
michael@0 4204 let callIndex = -1;
michael@0 4205
michael@0 4206 switch (info.code) {
michael@0 4207 case SUPP_SVC_NOTIFICATION_CODE2_PUT_ON_HOLD:
michael@0 4208 case SUPP_SVC_NOTIFICATION_CODE2_RETRIEVED:
michael@0 4209 notification = GECKO_SUPP_SVC_NOTIFICATION_FROM_CODE2[info.code];
michael@0 4210 break;
michael@0 4211 default:
michael@0 4212 // Notification type not supported.
michael@0 4213 return;
michael@0 4214 }
michael@0 4215
michael@0 4216 // Get the target call object for this notification.
michael@0 4217 let currentCallIndexes = Object.keys(this.currentCalls);
michael@0 4218 if (currentCallIndexes.length === 1) {
michael@0 4219 // Only one call exists. This should be the target.
michael@0 4220 callIndex = currentCallIndexes[0];
michael@0 4221 } else {
michael@0 4222 // Find the call in |currentCalls| by the given number.
michael@0 4223 if (info.number) {
michael@0 4224 for each (let currentCall in this.currentCalls) {
michael@0 4225 if (currentCall.number == info.number) {
michael@0 4226 callIndex = currentCall.callIndex;
michael@0 4227 break;
michael@0 4228 }
michael@0 4229 }
michael@0 4230 }
michael@0 4231 }
michael@0 4232
michael@0 4233 let message = {rilMessageType: "suppSvcNotification",
michael@0 4234 notification: notification,
michael@0 4235 callIndex: callIndex};
michael@0 4236 this.sendChromeMessage(message);
michael@0 4237 },
michael@0 4238
michael@0 4239 _cancelEmergencyCbModeTimeout: function() {
michael@0 4240 if (this._exitEmergencyCbModeTimeoutID) {
michael@0 4241 clearTimeout(this._exitEmergencyCbModeTimeoutID);
michael@0 4242 this._exitEmergencyCbModeTimeoutID = null;
michael@0 4243 }
michael@0 4244 },
michael@0 4245
michael@0 4246 _handleChangedEmergencyCbMode: function(active) {
michael@0 4247 this._isInEmergencyCbMode = active;
michael@0 4248
michael@0 4249 // Clear the existed timeout event.
michael@0 4250 this._cancelEmergencyCbModeTimeout();
michael@0 4251
michael@0 4252 // Start a new timeout event when entering the mode.
michael@0 4253 if (active) {
michael@0 4254 this._exitEmergencyCbModeTimeoutID = setTimeout(
michael@0 4255 this.exitEmergencyCbMode.bind(this), EMERGENCY_CB_MODE_TIMEOUT_MS);
michael@0 4256 }
michael@0 4257
michael@0 4258 let message = {rilMessageType: "emergencyCbModeChange",
michael@0 4259 active: active,
michael@0 4260 timeoutMs: EMERGENCY_CB_MODE_TIMEOUT_MS};
michael@0 4261 this.sendChromeMessage(message);
michael@0 4262 },
michael@0 4263
michael@0 4264 _processNetworks: function() {
michael@0 4265 let strings = this.context.Buf.readStringList();
michael@0 4266 let networks = [];
michael@0 4267
michael@0 4268 for (let i = 0; i < strings.length; i += 4) {
michael@0 4269 let network = {
michael@0 4270 longName: strings[i],
michael@0 4271 shortName: strings[i + 1],
michael@0 4272 mcc: null,
michael@0 4273 mnc: null,
michael@0 4274 state: null
michael@0 4275 };
michael@0 4276
michael@0 4277 let networkTuple = strings[i + 2];
michael@0 4278 try {
michael@0 4279 this._processNetworkTuple(networkTuple, network);
michael@0 4280 } catch (e) {
michael@0 4281 if (DEBUG) this.context.debug("Error processing operator tuple: " + e);
michael@0 4282 }
michael@0 4283
michael@0 4284 let state = strings[i + 3];
michael@0 4285 if (state === NETWORK_STATE_UNKNOWN) {
michael@0 4286 // TODO: looks like this might conflict in style with
michael@0 4287 // GECKO_NETWORK_STYLE_UNKNOWN / nsINetworkManager
michael@0 4288 state = GECKO_QAN_STATE_UNKNOWN;
michael@0 4289 }
michael@0 4290
michael@0 4291 network.state = state;
michael@0 4292 networks.push(network);
michael@0 4293 }
michael@0 4294 return networks;
michael@0 4295 },
michael@0 4296
michael@0 4297 /**
michael@0 4298 * The "numeric" portion of the operator info is a tuple
michael@0 4299 * containing MCC (country code) and MNC (network code).
michael@0 4300 * AFAICT, MCC should always be 3 digits, making the remaining
michael@0 4301 * portion the MNC.
michael@0 4302 */
michael@0 4303 _processNetworkTuple: function(networkTuple, network) {
michael@0 4304 let tupleLen = networkTuple.length;
michael@0 4305
michael@0 4306 if (tupleLen == 5 || tupleLen == 6) {
michael@0 4307 network.mcc = networkTuple.substr(0, 3);
michael@0 4308 network.mnc = networkTuple.substr(3);
michael@0 4309 } else {
michael@0 4310 network.mcc = null;
michael@0 4311 network.mnc = null;
michael@0 4312
michael@0 4313 throw new Error("Invalid network tuple (should be 5 or 6 digits): " + networkTuple);
michael@0 4314 }
michael@0 4315 },
michael@0 4316
michael@0 4317 /**
michael@0 4318 * Check if GSM radio access technology group.
michael@0 4319 */
michael@0 4320 _isGsmTechGroup: function(radioTech) {
michael@0 4321 if (!radioTech) {
michael@0 4322 return true;
michael@0 4323 }
michael@0 4324
michael@0 4325 switch(radioTech) {
michael@0 4326 case NETWORK_CREG_TECH_GPRS:
michael@0 4327 case NETWORK_CREG_TECH_EDGE:
michael@0 4328 case NETWORK_CREG_TECH_UMTS:
michael@0 4329 case NETWORK_CREG_TECH_HSDPA:
michael@0 4330 case NETWORK_CREG_TECH_HSUPA:
michael@0 4331 case NETWORK_CREG_TECH_HSPA:
michael@0 4332 case NETWORK_CREG_TECH_LTE:
michael@0 4333 case NETWORK_CREG_TECH_HSPAP:
michael@0 4334 case NETWORK_CREG_TECH_GSM:
michael@0 4335 return true;
michael@0 4336 }
michael@0 4337
michael@0 4338 return false;
michael@0 4339 },
michael@0 4340
michael@0 4341 /**
michael@0 4342 * Process radio technology change.
michael@0 4343 */
michael@0 4344 _processRadioTech: function(radioTech) {
michael@0 4345 let isCdma = !this._isGsmTechGroup(radioTech);
michael@0 4346 this.radioTech = radioTech;
michael@0 4347
michael@0 4348 if (DEBUG) {
michael@0 4349 this.context.debug("Radio tech is set to: " + GECKO_RADIO_TECH[radioTech] +
michael@0 4350 ", it is a " + (isCdma?"cdma":"gsm") + " technology");
michael@0 4351 }
michael@0 4352
michael@0 4353 // We should request SIM information when
michael@0 4354 // 1. Radio state has been changed, so we are waiting for radioTech or
michael@0 4355 // 2. isCdma is different from this._isCdma.
michael@0 4356 if (this._waitingRadioTech || isCdma != this._isCdma) {
michael@0 4357 this._isCdma = isCdma;
michael@0 4358 this._waitingRadioTech = false;
michael@0 4359 if (this._isCdma) {
michael@0 4360 this.getDeviceIdentity();
michael@0 4361 } else {
michael@0 4362 this.getIMEI();
michael@0 4363 this.getIMEISV();
michael@0 4364 }
michael@0 4365 this.getICCStatus();
michael@0 4366 }
michael@0 4367 },
michael@0 4368
michael@0 4369 /**
michael@0 4370 * Helper for returning the TOA for the given dial string.
michael@0 4371 */
michael@0 4372 _toaFromString: function(number) {
michael@0 4373 let toa = TOA_UNKNOWN;
michael@0 4374 if (number && number.length > 0 && number[0] == '+') {
michael@0 4375 toa = TOA_INTERNATIONAL;
michael@0 4376 }
michael@0 4377 return toa;
michael@0 4378 },
michael@0 4379
michael@0 4380 /**
michael@0 4381 * Helper for translating basic service group to call forwarding service class
michael@0 4382 * parameter.
michael@0 4383 */
michael@0 4384 _siToServiceClass: function(si) {
michael@0 4385 if (!si) {
michael@0 4386 return ICC_SERVICE_CLASS_NONE;
michael@0 4387 }
michael@0 4388
michael@0 4389 let serviceCode = parseInt(si, 10);
michael@0 4390 switch (serviceCode) {
michael@0 4391 case 10:
michael@0 4392 return ICC_SERVICE_CLASS_SMS + ICC_SERVICE_CLASS_FAX + ICC_SERVICE_CLASS_VOICE;
michael@0 4393 case 11:
michael@0 4394 return ICC_SERVICE_CLASS_VOICE;
michael@0 4395 case 12:
michael@0 4396 return ICC_SERVICE_CLASS_SMS + ICC_SERVICE_CLASS_FAX;
michael@0 4397 case 13:
michael@0 4398 return ICC_SERVICE_CLASS_FAX;
michael@0 4399 case 16:
michael@0 4400 return ICC_SERVICE_CLASS_SMS;
michael@0 4401 case 19:
michael@0 4402 return ICC_SERVICE_CLASS_FAX + ICC_SERVICE_CLASS_VOICE;
michael@0 4403 case 21:
michael@0 4404 return ICC_SERVICE_CLASS_PAD + ICC_SERVICE_CLASS_DATA_ASYNC;
michael@0 4405 case 22:
michael@0 4406 return ICC_SERVICE_CLASS_PACKET + ICC_SERVICE_CLASS_DATA_SYNC;
michael@0 4407 case 25:
michael@0 4408 return ICC_SERVICE_CLASS_DATA_ASYNC;
michael@0 4409 case 26:
michael@0 4410 return ICC_SERVICE_CLASS_DATA_SYNC + SERVICE_CLASS_VOICE;
michael@0 4411 case 99:
michael@0 4412 return ICC_SERVICE_CLASS_PACKET;
michael@0 4413 default:
michael@0 4414 return ICC_SERVICE_CLASS_NONE;
michael@0 4415 }
michael@0 4416 },
michael@0 4417
michael@0 4418 /**
michael@0 4419 * @param message A decoded SMS-DELIVER message.
michael@0 4420 *
michael@0 4421 * @see 3GPP TS 31.111 section 7.1.1
michael@0 4422 */
michael@0 4423 dataDownloadViaSMSPP: function(message) {
michael@0 4424 let Buf = this.context.Buf;
michael@0 4425 let GsmPDUHelper = this.context.GsmPDUHelper;
michael@0 4426
michael@0 4427 let options = {
michael@0 4428 pid: message.pid,
michael@0 4429 dcs: message.dcs,
michael@0 4430 encoding: message.encoding,
michael@0 4431 };
michael@0 4432 Buf.newParcel(REQUEST_STK_SEND_ENVELOPE_WITH_STATUS, options);
michael@0 4433
michael@0 4434 Buf.seekIncoming(-1 * (Buf.getCurrentParcelSize() - Buf.getReadAvailable()
michael@0 4435 - 2 * Buf.UINT32_SIZE)); // Skip response_type & request_type.
michael@0 4436 let messageStringLength = Buf.readInt32(); // In semi-octets
michael@0 4437 let smscLength = GsmPDUHelper.readHexOctet(); // In octets, inclusive of TOA
michael@0 4438 let tpduLength = (messageStringLength / 2) - (smscLength + 1); // In octets
michael@0 4439
michael@0 4440 // Device identities: 4 bytes
michael@0 4441 // Address: 0 or (2 + smscLength)
michael@0 4442 // SMS TPDU: (2 or 3) + tpduLength
michael@0 4443 let berLen = 4 +
michael@0 4444 (smscLength ? (2 + smscLength) : 0) +
michael@0 4445 (tpduLength <= 127 ? 2 : 3) + tpduLength; // In octets
michael@0 4446
michael@0 4447 let parcelLength = (berLen <= 127 ? 2 : 3) + berLen; // In octets
michael@0 4448 Buf.writeInt32(parcelLength * 2); // In semi-octets
michael@0 4449
michael@0 4450 // Write a BER-TLV
michael@0 4451 GsmPDUHelper.writeHexOctet(BER_SMS_PP_DOWNLOAD_TAG);
michael@0 4452 if (berLen > 127) {
michael@0 4453 GsmPDUHelper.writeHexOctet(0x81);
michael@0 4454 }
michael@0 4455 GsmPDUHelper.writeHexOctet(berLen);
michael@0 4456
michael@0 4457 // Device Identifies-TLV
michael@0 4458 GsmPDUHelper.writeHexOctet(COMPREHENSIONTLV_TAG_DEVICE_ID |
michael@0 4459 COMPREHENSIONTLV_FLAG_CR);
michael@0 4460 GsmPDUHelper.writeHexOctet(0x02);
michael@0 4461 GsmPDUHelper.writeHexOctet(STK_DEVICE_ID_NETWORK);
michael@0 4462 GsmPDUHelper.writeHexOctet(STK_DEVICE_ID_SIM);
michael@0 4463
michael@0 4464 // Address-TLV
michael@0 4465 if (smscLength) {
michael@0 4466 GsmPDUHelper.writeHexOctet(COMPREHENSIONTLV_TAG_ADDRESS);
michael@0 4467 GsmPDUHelper.writeHexOctet(smscLength);
michael@0 4468 Buf.copyIncomingToOutgoing(Buf.PDU_HEX_OCTET_SIZE * smscLength);
michael@0 4469 }
michael@0 4470
michael@0 4471 // SMS TPDU-TLV
michael@0 4472 GsmPDUHelper.writeHexOctet(COMPREHENSIONTLV_TAG_SMS_TPDU |
michael@0 4473 COMPREHENSIONTLV_FLAG_CR);
michael@0 4474 if (tpduLength > 127) {
michael@0 4475 GsmPDUHelper.writeHexOctet(0x81);
michael@0 4476 }
michael@0 4477 GsmPDUHelper.writeHexOctet(tpduLength);
michael@0 4478 Buf.copyIncomingToOutgoing(Buf.PDU_HEX_OCTET_SIZE * tpduLength);
michael@0 4479
michael@0 4480 // Write 2 string delimitors for the total string length must be even.
michael@0 4481 Buf.writeStringDelimiter(0);
michael@0 4482
michael@0 4483 Buf.sendParcel();
michael@0 4484 },
michael@0 4485
michael@0 4486 /**
michael@0 4487 * @param success A boolean value indicating the result of previous
michael@0 4488 * SMS-DELIVER message handling.
michael@0 4489 * @param responsePduLen ICC IO response PDU length in octets.
michael@0 4490 * @param options An object that contains four attributes: `pid`, `dcs`,
michael@0 4491 * `encoding` and `responsePduLen`.
michael@0 4492 *
michael@0 4493 * @see 3GPP TS 23.040 section 9.2.2.1a
michael@0 4494 */
michael@0 4495 acknowledgeIncomingGsmSmsWithPDU: function(success, responsePduLen, options) {
michael@0 4496 let Buf = this.context.Buf;
michael@0 4497 let GsmPDUHelper = this.context.GsmPDUHelper;
michael@0 4498
michael@0 4499 Buf.newParcel(REQUEST_ACKNOWLEDGE_INCOMING_GSM_SMS_WITH_PDU);
michael@0 4500
michael@0 4501 // Two strings.
michael@0 4502 Buf.writeInt32(2);
michael@0 4503
michael@0 4504 // String 1: Success
michael@0 4505 Buf.writeString(success ? "1" : "0");
michael@0 4506
michael@0 4507 // String 2: RP-ACK/RP-ERROR PDU
michael@0 4508 Buf.writeInt32(2 * (responsePduLen + (success ? 5 : 6))); // In semi-octet
michael@0 4509 // 1. TP-MTI & TP-UDHI
michael@0 4510 GsmPDUHelper.writeHexOctet(PDU_MTI_SMS_DELIVER);
michael@0 4511 if (!success) {
michael@0 4512 // 2. TP-FCS
michael@0 4513 GsmPDUHelper.writeHexOctet(PDU_FCS_USIM_DATA_DOWNLOAD_ERROR);
michael@0 4514 }
michael@0 4515 // 3. TP-PI
michael@0 4516 GsmPDUHelper.writeHexOctet(PDU_PI_USER_DATA_LENGTH |
michael@0 4517 PDU_PI_DATA_CODING_SCHEME |
michael@0 4518 PDU_PI_PROTOCOL_IDENTIFIER);
michael@0 4519 // 4. TP-PID
michael@0 4520 GsmPDUHelper.writeHexOctet(options.pid);
michael@0 4521 // 5. TP-DCS
michael@0 4522 GsmPDUHelper.writeHexOctet(options.dcs);
michael@0 4523 // 6. TP-UDL
michael@0 4524 if (options.encoding == PDU_DCS_MSG_CODING_7BITS_ALPHABET) {
michael@0 4525 GsmPDUHelper.writeHexOctet(Math.floor(responsePduLen * 8 / 7));
michael@0 4526 } else {
michael@0 4527 GsmPDUHelper.writeHexOctet(responsePduLen);
michael@0 4528 }
michael@0 4529 // TP-UD
michael@0 4530 Buf.copyIncomingToOutgoing(Buf.PDU_HEX_OCTET_SIZE * responsePduLen);
michael@0 4531 // Write 2 string delimitors for the total string length must be even.
michael@0 4532 Buf.writeStringDelimiter(0);
michael@0 4533
michael@0 4534 Buf.sendParcel();
michael@0 4535 },
michael@0 4536
michael@0 4537 /**
michael@0 4538 * @param message A decoded SMS-DELIVER message.
michael@0 4539 */
michael@0 4540 writeSmsToSIM: function(message) {
michael@0 4541 let Buf = this.context.Buf;
michael@0 4542 let GsmPDUHelper = this.context.GsmPDUHelper;
michael@0 4543
michael@0 4544 Buf.newParcel(REQUEST_WRITE_SMS_TO_SIM);
michael@0 4545
michael@0 4546 // Write EFsms Status
michael@0 4547 Buf.writeInt32(EFSMS_STATUS_FREE);
michael@0 4548
michael@0 4549 Buf.seekIncoming(-1 * (Buf.getCurrentParcelSize() - Buf.getReadAvailable()
michael@0 4550 - 2 * Buf.UINT32_SIZE)); // Skip response_type & request_type.
michael@0 4551 let messageStringLength = Buf.readInt32(); // In semi-octets
michael@0 4552 let smscLength = GsmPDUHelper.readHexOctet(); // In octets, inclusive of TOA
michael@0 4553 let pduLength = (messageStringLength / 2) - (smscLength + 1); // In octets
michael@0 4554
michael@0 4555 // 1. Write PDU first.
michael@0 4556 if (smscLength > 0) {
michael@0 4557 Buf.seekIncoming(smscLength * Buf.PDU_HEX_OCTET_SIZE);
michael@0 4558 }
michael@0 4559 // Write EFsms PDU string length
michael@0 4560 Buf.writeInt32(2 * pduLength); // In semi-octets
michael@0 4561 if (pduLength) {
michael@0 4562 Buf.copyIncomingToOutgoing(Buf.PDU_HEX_OCTET_SIZE * pduLength);
michael@0 4563 }
michael@0 4564 // Write 2 string delimitors for the total string length must be even.
michael@0 4565 Buf.writeStringDelimiter(0);
michael@0 4566
michael@0 4567 // 2. Write SMSC
michael@0 4568 // Write EFsms SMSC string length
michael@0 4569 Buf.writeInt32(2 * (smscLength + 1)); // Plus smscLength itself, in semi-octets
michael@0 4570 // Write smscLength
michael@0 4571 GsmPDUHelper.writeHexOctet(smscLength);
michael@0 4572 // Write TOA & SMSC Address
michael@0 4573 if (smscLength) {
michael@0 4574 Buf.seekIncoming(-1 * (Buf.getCurrentParcelSize() - Buf.getReadAvailable()
michael@0 4575 - 2 * Buf.UINT32_SIZE // Skip response_type, request_type.
michael@0 4576 - 2 * Buf.PDU_HEX_OCTET_SIZE)); // Skip messageStringLength & smscLength.
michael@0 4577 Buf.copyIncomingToOutgoing(Buf.PDU_HEX_OCTET_SIZE * smscLength);
michael@0 4578 }
michael@0 4579 // Write 2 string delimitors for the total string length must be even.
michael@0 4580 Buf.writeStringDelimiter(0);
michael@0 4581
michael@0 4582 Buf.sendParcel();
michael@0 4583 },
michael@0 4584
michael@0 4585 /**
michael@0 4586 * Helper to delegate the received sms segment to RadioInterface to process.
michael@0 4587 *
michael@0 4588 * @param message
michael@0 4589 * Received sms message.
michael@0 4590 *
michael@0 4591 * @return MOZ_FCS_WAIT_FOR_EXPLICIT_ACK
michael@0 4592 */
michael@0 4593 _processSmsMultipart: function(message) {
michael@0 4594 message.rilMessageType = "sms-received";
michael@0 4595
michael@0 4596 this.sendChromeMessage(message);
michael@0 4597
michael@0 4598 return MOZ_FCS_WAIT_FOR_EXPLICIT_ACK;
michael@0 4599 },
michael@0 4600
michael@0 4601 /**
michael@0 4602 * Helper for processing SMS-STATUS-REPORT PDUs.
michael@0 4603 *
michael@0 4604 * @param length
michael@0 4605 * Length of SMS string in the incoming parcel.
michael@0 4606 *
michael@0 4607 * @return A failure cause defined in 3GPP 23.040 clause 9.2.3.22.
michael@0 4608 */
michael@0 4609 _processSmsStatusReport: function(length) {
michael@0 4610 let [message, result] = this.context.GsmPDUHelper.processReceivedSms(length);
michael@0 4611 if (!message) {
michael@0 4612 if (DEBUG) this.context.debug("invalid SMS-STATUS-REPORT");
michael@0 4613 return PDU_FCS_UNSPECIFIED;
michael@0 4614 }
michael@0 4615
michael@0 4616 let options = this._pendingSentSmsMap[message.messageRef];
michael@0 4617 if (!options) {
michael@0 4618 if (DEBUG) this.context.debug("no pending SMS-SUBMIT message");
michael@0 4619 return PDU_FCS_OK;
michael@0 4620 }
michael@0 4621
michael@0 4622 let status = message.status;
michael@0 4623
michael@0 4624 // 3GPP TS 23.040 9.2.3.15 `The MS shall interpret any reserved values as
michael@0 4625 // "Service Rejected"(01100011) but shall store them exactly as received.`
michael@0 4626 if ((status >= 0x80)
michael@0 4627 || ((status >= PDU_ST_0_RESERVED_BEGIN)
michael@0 4628 && (status < PDU_ST_0_SC_SPECIFIC_BEGIN))
michael@0 4629 || ((status >= PDU_ST_1_RESERVED_BEGIN)
michael@0 4630 && (status < PDU_ST_1_SC_SPECIFIC_BEGIN))
michael@0 4631 || ((status >= PDU_ST_2_RESERVED_BEGIN)
michael@0 4632 && (status < PDU_ST_2_SC_SPECIFIC_BEGIN))
michael@0 4633 || ((status >= PDU_ST_3_RESERVED_BEGIN)
michael@0 4634 && (status < PDU_ST_3_SC_SPECIFIC_BEGIN))
michael@0 4635 ) {
michael@0 4636 status = PDU_ST_3_SERVICE_REJECTED;
michael@0 4637 }
michael@0 4638
michael@0 4639 // Pending. Waiting for next status report.
michael@0 4640 if ((status >>> 5) == 0x01) {
michael@0 4641 if (DEBUG) this.context.debug("SMS-STATUS-REPORT: delivery still pending");
michael@0 4642 return PDU_FCS_OK;
michael@0 4643 }
michael@0 4644
michael@0 4645 delete this._pendingSentSmsMap[message.messageRef];
michael@0 4646
michael@0 4647 let deliveryStatus = ((status >>> 5) === 0x00)
michael@0 4648 ? GECKO_SMS_DELIVERY_STATUS_SUCCESS
michael@0 4649 : GECKO_SMS_DELIVERY_STATUS_ERROR;
michael@0 4650 this.sendChromeMessage({
michael@0 4651 rilMessageType: options.rilMessageType,
michael@0 4652 rilMessageToken: options.rilMessageToken,
michael@0 4653 deliveryStatus: deliveryStatus
michael@0 4654 });
michael@0 4655
michael@0 4656 return PDU_FCS_OK;
michael@0 4657 },
michael@0 4658
michael@0 4659 /**
michael@0 4660 * Helper for processing CDMA SMS Delivery Acknowledgment Message
michael@0 4661 *
michael@0 4662 * @param message
michael@0 4663 * decoded SMS Delivery ACK message from CdmaPDUHelper.
michael@0 4664 *
michael@0 4665 * @return A failure cause defined in 3GPP 23.040 clause 9.2.3.22.
michael@0 4666 */
michael@0 4667 _processCdmaSmsStatusReport: function(message) {
michael@0 4668 let options = this._pendingSentSmsMap[message.msgId];
michael@0 4669 if (!options) {
michael@0 4670 if (DEBUG) this.context.debug("no pending SMS-SUBMIT message");
michael@0 4671 return PDU_FCS_OK;
michael@0 4672 }
michael@0 4673
michael@0 4674 if (message.errorClass === 2) {
michael@0 4675 if (DEBUG) {
michael@0 4676 this.context.debug("SMS-STATUS-REPORT: delivery still pending, " +
michael@0 4677 "msgStatus: " + message.msgStatus);
michael@0 4678 }
michael@0 4679 return PDU_FCS_OK;
michael@0 4680 }
michael@0 4681
michael@0 4682 delete this._pendingSentSmsMap[message.msgId];
michael@0 4683
michael@0 4684 if (message.errorClass === -1 && message.body) {
michael@0 4685 // Process as normal incoming SMS, if errorClass is invalid
michael@0 4686 // but message body is available.
michael@0 4687 return this._processSmsMultipart(message);
michael@0 4688 }
michael@0 4689
michael@0 4690 let deliveryStatus = (message.errorClass === 0)
michael@0 4691 ? GECKO_SMS_DELIVERY_STATUS_SUCCESS
michael@0 4692 : GECKO_SMS_DELIVERY_STATUS_ERROR;
michael@0 4693 this.sendChromeMessage({
michael@0 4694 rilMessageType: options.rilMessageType,
michael@0 4695 rilMessageToken: options.rilMessageToken,
michael@0 4696 deliveryStatus: deliveryStatus
michael@0 4697 });
michael@0 4698
michael@0 4699 return PDU_FCS_OK;
michael@0 4700 },
michael@0 4701
michael@0 4702 /**
michael@0 4703 * Helper for processing CDMA SMS WAP Push Message
michael@0 4704 *
michael@0 4705 * @param message
michael@0 4706 * decoded WAP message from CdmaPDUHelper.
michael@0 4707 *
michael@0 4708 * @return A failure cause defined in 3GPP 23.040 clause 9.2.3.22.
michael@0 4709 */
michael@0 4710 _processCdmaSmsWapPush: function(message) {
michael@0 4711 if (!message.data) {
michael@0 4712 if (DEBUG) this.context.debug("no data inside WAP Push message.");
michael@0 4713 return PDU_FCS_OK;
michael@0 4714 }
michael@0 4715
michael@0 4716 // See 6.5. MAPPING OF WDP TO CDMA SMS in WAP-295-WDP.
michael@0 4717 //
michael@0 4718 // Field | Length (bits)
michael@0 4719 // -----------------------------------------
michael@0 4720 // MSG_TYPE | 8
michael@0 4721 // TOTAL_SEGMENTS | 8
michael@0 4722 // SEGMENT_NUMBER | 8
michael@0 4723 // DATAGRAM | (NUM_FIELDS – 3) * 8
michael@0 4724 let index = 0;
michael@0 4725 if (message.data[index++] !== 0) {
michael@0 4726 if (DEBUG) this.context.debug("Ignore a WAP Message which is not WDP.");
michael@0 4727 return PDU_FCS_OK;
michael@0 4728 }
michael@0 4729
michael@0 4730 // 1. Originator Address in SMS-TL + Message_Id in SMS-TS are used to identify a unique WDP datagram.
michael@0 4731 // 2. TOTAL_SEGMENTS, SEGMENT_NUMBER are used to verify that a complete
michael@0 4732 // datagram has been received and is ready to be passed to a higher layer.
michael@0 4733 message.header = {
michael@0 4734 segmentRef: message.msgId,
michael@0 4735 segmentMaxSeq: message.data[index++],
michael@0 4736 segmentSeq: message.data[index++] + 1 // It's zero-based in CDMA WAP Push.
michael@0 4737 };
michael@0 4738
michael@0 4739 if (message.header.segmentSeq > message.header.segmentMaxSeq) {
michael@0 4740 if (DEBUG) this.context.debug("Wrong WDP segment info.");
michael@0 4741 return PDU_FCS_OK;
michael@0 4742 }
michael@0 4743
michael@0 4744 // Ports are only specified in 1st segment.
michael@0 4745 if (message.header.segmentSeq == 1) {
michael@0 4746 message.header.originatorPort = message.data[index++] << 8;
michael@0 4747 message.header.originatorPort |= message.data[index++];
michael@0 4748 message.header.destinationPort = message.data[index++] << 8;
michael@0 4749 message.header.destinationPort |= message.data[index++];
michael@0 4750 }
michael@0 4751
michael@0 4752 message.data = message.data.subarray(index);
michael@0 4753
michael@0 4754 return this._processSmsMultipart(message);
michael@0 4755 },
michael@0 4756
michael@0 4757 /**
michael@0 4758 * Helper for processing sent multipart SMS.
michael@0 4759 */
michael@0 4760 _processSentSmsSegment: function(options) {
michael@0 4761 // Setup attributes for sending next segment
michael@0 4762 let next = options.segmentSeq;
michael@0 4763 options.body = options.segments[next].body;
michael@0 4764 options.encodedBodyLength = options.segments[next].encodedBodyLength;
michael@0 4765 options.segmentSeq = next + 1;
michael@0 4766
michael@0 4767 this.sendSMS(options);
michael@0 4768 },
michael@0 4769
michael@0 4770 /**
michael@0 4771 * Helper for processing result of send SMS.
michael@0 4772 *
michael@0 4773 * @param length
michael@0 4774 * Length of SMS string in the incoming parcel.
michael@0 4775 * @param options
michael@0 4776 * Sms information.
michael@0 4777 */
michael@0 4778 _processSmsSendResult: function(length, options) {
michael@0 4779 if (options.rilRequestError) {
michael@0 4780 if (DEBUG) {
michael@0 4781 this.context.debug("_processSmsSendResult: rilRequestError = " +
michael@0 4782 options.rilRequestError);
michael@0 4783 }
michael@0 4784 switch (options.rilRequestError) {
michael@0 4785 case ERROR_SMS_SEND_FAIL_RETRY:
michael@0 4786 if (options.retryCount < SMS_RETRY_MAX) {
michael@0 4787 options.retryCount++;
michael@0 4788 // TODO: bug 736702 TP-MR, retry interval, retry timeout
michael@0 4789 this.sendSMS(options);
michael@0 4790 break;
michael@0 4791 }
michael@0 4792 // Fallback to default error handling if it meets max retry count.
michael@0 4793 // Fall through.
michael@0 4794 default:
michael@0 4795 this.sendChromeMessage({
michael@0 4796 rilMessageType: options.rilMessageType,
michael@0 4797 rilMessageToken: options.rilMessageToken,
michael@0 4798 errorMsg: options.rilRequestError,
michael@0 4799 });
michael@0 4800 break;
michael@0 4801 }
michael@0 4802 return;
michael@0 4803 }
michael@0 4804
michael@0 4805 let Buf = this.context.Buf;
michael@0 4806 options.messageRef = Buf.readInt32();
michael@0 4807 options.ackPDU = Buf.readString();
michael@0 4808 options.errorCode = Buf.readInt32();
michael@0 4809
michael@0 4810 if ((options.segmentMaxSeq > 1)
michael@0 4811 && (options.segmentSeq < options.segmentMaxSeq)) {
michael@0 4812 // Not last segment
michael@0 4813 this._processSentSmsSegment(options);
michael@0 4814 } else {
michael@0 4815 // Last segment sent with success.
michael@0 4816 if (options.requestStatusReport) {
michael@0 4817 if (DEBUG) {
michael@0 4818 this.context.debug("waiting SMS-STATUS-REPORT for messageRef " +
michael@0 4819 options.messageRef);
michael@0 4820 }
michael@0 4821 this._pendingSentSmsMap[options.messageRef] = options;
michael@0 4822 }
michael@0 4823
michael@0 4824 this.sendChromeMessage({
michael@0 4825 rilMessageType: options.rilMessageType,
michael@0 4826 rilMessageToken: options.rilMessageToken,
michael@0 4827 });
michael@0 4828 }
michael@0 4829 },
michael@0 4830
michael@0 4831 _processReceivedSmsCbPage: function(original) {
michael@0 4832 if (original.numPages <= 1) {
michael@0 4833 if (original.body) {
michael@0 4834 original.fullBody = original.body;
michael@0 4835 delete original.body;
michael@0 4836 } else if (original.data) {
michael@0 4837 original.fullData = original.data;
michael@0 4838 delete original.data;
michael@0 4839 }
michael@0 4840 return original;
michael@0 4841 }
michael@0 4842
michael@0 4843 // Hash = <serial>:<mcc>:<mnc>:<lac>:<cid>
michael@0 4844 let hash = original.serial + ":" + this.iccInfo.mcc + ":"
michael@0 4845 + this.iccInfo.mnc + ":";
michael@0 4846 switch (original.geographicalScope) {
michael@0 4847 case CB_GSM_GEOGRAPHICAL_SCOPE_CELL_WIDE_IMMEDIATE:
michael@0 4848 case CB_GSM_GEOGRAPHICAL_SCOPE_CELL_WIDE:
michael@0 4849 hash += this.voiceRegistrationState.cell.gsmLocationAreaCode + ":"
michael@0 4850 + this.voiceRegistrationState.cell.gsmCellId;
michael@0 4851 break;
michael@0 4852 case CB_GSM_GEOGRAPHICAL_SCOPE_LOCATION_AREA_WIDE:
michael@0 4853 hash += this.voiceRegistrationState.cell.gsmLocationAreaCode + ":";
michael@0 4854 break;
michael@0 4855 default:
michael@0 4856 hash += ":";
michael@0 4857 break;
michael@0 4858 }
michael@0 4859
michael@0 4860 let index = original.pageIndex;
michael@0 4861
michael@0 4862 let options = this._receivedSmsCbPagesMap[hash];
michael@0 4863 if (!options) {
michael@0 4864 options = original;
michael@0 4865 this._receivedSmsCbPagesMap[hash] = options;
michael@0 4866
michael@0 4867 options.receivedPages = 0;
michael@0 4868 options.pages = [];
michael@0 4869 } else if (options.pages[index]) {
michael@0 4870 // Duplicated page?
michael@0 4871 if (DEBUG) {
michael@0 4872 this.context.debug("Got duplicated page no." + index +
michael@0 4873 " of a multipage SMSCB: " + JSON.stringify(original));
michael@0 4874 }
michael@0 4875 return null;
michael@0 4876 }
michael@0 4877
michael@0 4878 if (options.encoding == PDU_DCS_MSG_CODING_8BITS_ALPHABET) {
michael@0 4879 options.pages[index] = original.data;
michael@0 4880 delete original.data;
michael@0 4881 } else {
michael@0 4882 options.pages[index] = original.body;
michael@0 4883 delete original.body;
michael@0 4884 }
michael@0 4885 options.receivedPages++;
michael@0 4886 if (options.receivedPages < options.numPages) {
michael@0 4887 if (DEBUG) {
michael@0 4888 this.context.debug("Got page no." + index + " of a multipage SMSCB: " +
michael@0 4889 JSON.stringify(options));
michael@0 4890 }
michael@0 4891 return null;
michael@0 4892 }
michael@0 4893
michael@0 4894 // Remove from map
michael@0 4895 delete this._receivedSmsCbPagesMap[hash];
michael@0 4896
michael@0 4897 // Rebuild full body
michael@0 4898 if (options.encoding == PDU_DCS_MSG_CODING_8BITS_ALPHABET) {
michael@0 4899 // Uint8Array doesn't have `concat`, so we have to merge all pages by hand.
michael@0 4900 let fullDataLen = 0;
michael@0 4901 for (let i = 1; i <= options.numPages; i++) {
michael@0 4902 fullDataLen += options.pages[i].length;
michael@0 4903 }
michael@0 4904
michael@0 4905 options.fullData = new Uint8Array(fullDataLen);
michael@0 4906 for (let d= 0, i = 1; i <= options.numPages; i++) {
michael@0 4907 let data = options.pages[i];
michael@0 4908 for (let j = 0; j < data.length; j++) {
michael@0 4909 options.fullData[d++] = data[j];
michael@0 4910 }
michael@0 4911 }
michael@0 4912 } else {
michael@0 4913 options.fullBody = options.pages.join("");
michael@0 4914 }
michael@0 4915
michael@0 4916 if (DEBUG) {
michael@0 4917 this.context.debug("Got full multipage SMSCB: " + JSON.stringify(options));
michael@0 4918 }
michael@0 4919
michael@0 4920 return options;
michael@0 4921 },
michael@0 4922
michael@0 4923 _mergeCellBroadcastConfigs: function(list, from, to) {
michael@0 4924 if (!list) {
michael@0 4925 return [from, to];
michael@0 4926 }
michael@0 4927
michael@0 4928 for (let i = 0, f1, t1; i < list.length;) {
michael@0 4929 f1 = list[i++];
michael@0 4930 t1 = list[i++];
michael@0 4931 if (to == f1) {
michael@0 4932 // ...[from]...[to|f1]...(t1)
michael@0 4933 list[i - 2] = from;
michael@0 4934 return list;
michael@0 4935 }
michael@0 4936
michael@0 4937 if (to < f1) {
michael@0 4938 // ...[from]...(to)...[f1] or ...[from]...(to)[f1]
michael@0 4939 if (i > 2) {
michael@0 4940 // Not the first range pair, merge three arrays.
michael@0 4941 return list.slice(0, i - 2).concat([from, to]).concat(list.slice(i - 2));
michael@0 4942 } else {
michael@0 4943 return [from, to].concat(list);
michael@0 4944 }
michael@0 4945 }
michael@0 4946
michael@0 4947 if (from > t1) {
michael@0 4948 // ...[f1]...(t1)[from] or ...[f1]...(t1)...[from]
michael@0 4949 continue;
michael@0 4950 }
michael@0 4951
michael@0 4952 // Have overlap or merge-able adjacency with [f1]...(t1). Replace it
michael@0 4953 // with [min(from, f1)]...(max(to, t1)).
michael@0 4954
michael@0 4955 let changed = false;
michael@0 4956 if (from < f1) {
michael@0 4957 // [from]...[f1]...(t1) or [from][f1]...(t1)
michael@0 4958 // Save minimum from value.
michael@0 4959 list[i - 2] = from;
michael@0 4960 changed = true;
michael@0 4961 }
michael@0 4962
michael@0 4963 if (to <= t1) {
michael@0 4964 // [from]...[to](t1) or [from]...(to|t1)
michael@0 4965 // Can't have further merge-able adjacency. Return.
michael@0 4966 return list;
michael@0 4967 }
michael@0 4968
michael@0 4969 // Try merging possible next adjacent range.
michael@0 4970 let j = i;
michael@0 4971 for (let f2, t2; j < list.length;) {
michael@0 4972 f2 = list[j++];
michael@0 4973 t2 = list[j++];
michael@0 4974 if (to > t2) {
michael@0 4975 // [from]...[f2]...[t2]...(to) or [from]...[f2]...[t2](to)
michael@0 4976 // Merge next adjacent range again.
michael@0 4977 continue;
michael@0 4978 }
michael@0 4979
michael@0 4980 if (to < t2) {
michael@0 4981 if (to < f2) {
michael@0 4982 // [from]...(to)[f2] or [from]...(to)...[f2]
michael@0 4983 // Roll back and give up.
michael@0 4984 j -= 2;
michael@0 4985 } else if (to < t2) {
michael@0 4986 // [from]...[to|f2]...(t2), or [from]...[f2]...[to](t2)
michael@0 4987 // Merge to [from]...(t2) and give up.
michael@0 4988 to = t2;
michael@0 4989 }
michael@0 4990 }
michael@0 4991
michael@0 4992 break;
michael@0 4993 }
michael@0 4994
michael@0 4995 // Save maximum to value.
michael@0 4996 list[i - 1] = to;
michael@0 4997
michael@0 4998 if (j != i) {
michael@0 4999 // Remove merged adjacent ranges.
michael@0 5000 let ret = list.slice(0, i);
michael@0 5001 if (j < list.length) {
michael@0 5002 ret = ret.concat(list.slice(j));
michael@0 5003 }
michael@0 5004 return ret;
michael@0 5005 }
michael@0 5006
michael@0 5007 return list;
michael@0 5008 }
michael@0 5009
michael@0 5010 // Append to the end.
michael@0 5011 list.push(from);
michael@0 5012 list.push(to);
michael@0 5013
michael@0 5014 return list;
michael@0 5015 },
michael@0 5016
michael@0 5017 _isCellBroadcastConfigReady: function() {
michael@0 5018 if (!("MMI" in this.cellBroadcastConfigs)) {
michael@0 5019 return false;
michael@0 5020 }
michael@0 5021
michael@0 5022 // CBMI should be ready in GSM.
michael@0 5023 if (!this._isCdma &&
michael@0 5024 (!("CBMI" in this.cellBroadcastConfigs) ||
michael@0 5025 !("CBMID" in this.cellBroadcastConfigs) ||
michael@0 5026 !("CBMIR" in this.cellBroadcastConfigs))) {
michael@0 5027 return false;
michael@0 5028 }
michael@0 5029
michael@0 5030 return true;
michael@0 5031 },
michael@0 5032
michael@0 5033 /**
michael@0 5034 * Merge all members of cellBroadcastConfigs into mergedCellBroadcastConfig.
michael@0 5035 */
michael@0 5036 _mergeAllCellBroadcastConfigs: function() {
michael@0 5037 if (!this._isCellBroadcastConfigReady()) {
michael@0 5038 if (DEBUG) {
michael@0 5039 this.context.debug("cell broadcast configs not ready, waiting ...");
michael@0 5040 }
michael@0 5041 return;
michael@0 5042 }
michael@0 5043
michael@0 5044 // Prepare cell broadcast config. CBMI* are only used in GSM.
michael@0 5045 let usedCellBroadcastConfigs = {MMI: this.cellBroadcastConfigs.MMI};
michael@0 5046 if (!this._isCdma) {
michael@0 5047 usedCellBroadcastConfigs.CBMI = this.cellBroadcastConfigs.CBMI;
michael@0 5048 usedCellBroadcastConfigs.CBMID = this.cellBroadcastConfigs.CBMID;
michael@0 5049 usedCellBroadcastConfigs.CBMIR = this.cellBroadcastConfigs.CBMIR;
michael@0 5050 }
michael@0 5051
michael@0 5052 if (DEBUG) {
michael@0 5053 this.context.debug("Cell Broadcast search lists: " +
michael@0 5054 JSON.stringify(usedCellBroadcastConfigs));
michael@0 5055 }
michael@0 5056
michael@0 5057 let list = null;
michael@0 5058 for each (let ll in usedCellBroadcastConfigs) {
michael@0 5059 if (ll == null) {
michael@0 5060 continue;
michael@0 5061 }
michael@0 5062
michael@0 5063 for (let i = 0; i < ll.length; i += 2) {
michael@0 5064 list = this._mergeCellBroadcastConfigs(list, ll[i], ll[i + 1]);
michael@0 5065 }
michael@0 5066 }
michael@0 5067
michael@0 5068 if (DEBUG) {
michael@0 5069 this.context.debug("Cell Broadcast search lists(merged): " +
michael@0 5070 JSON.stringify(list));
michael@0 5071 }
michael@0 5072 this.mergedCellBroadcastConfig = list;
michael@0 5073 this.updateCellBroadcastConfig();
michael@0 5074 },
michael@0 5075
michael@0 5076 /**
michael@0 5077 * Check whether search list from settings is settable by MMI, that is,
michael@0 5078 * whether the range is bounded in any entries of CB_NON_MMI_SETTABLE_RANGES.
michael@0 5079 */
michael@0 5080 _checkCellBroadcastMMISettable: function(from, to) {
michael@0 5081 if ((to <= from) || (from >= 65536) || (from < 0)) {
michael@0 5082 return false;
michael@0 5083 }
michael@0 5084
michael@0 5085 if (!this._isCdma) {
michael@0 5086 // GSM not settable ranges.
michael@0 5087 for (let i = 0, f, t; i < CB_NON_MMI_SETTABLE_RANGES.length;) {
michael@0 5088 f = CB_NON_MMI_SETTABLE_RANGES[i++];
michael@0 5089 t = CB_NON_MMI_SETTABLE_RANGES[i++];
michael@0 5090 if ((from < t) && (to > f)) {
michael@0 5091 // Have overlap.
michael@0 5092 return false;
michael@0 5093 }
michael@0 5094 }
michael@0 5095 }
michael@0 5096
michael@0 5097 return true;
michael@0 5098 },
michael@0 5099
michael@0 5100 /**
michael@0 5101 * Convert Cell Broadcast settings string into search list.
michael@0 5102 */
michael@0 5103 _convertCellBroadcastSearchList: function(searchListStr) {
michael@0 5104 let parts = searchListStr && searchListStr.split(",");
michael@0 5105 if (!parts) {
michael@0 5106 return null;
michael@0 5107 }
michael@0 5108
michael@0 5109 let list = null;
michael@0 5110 let result, from, to;
michael@0 5111 for (let range of parts) {
michael@0 5112 // Match "12" or "12-34". The result will be ["12", "12", null] or
michael@0 5113 // ["12-34", "12", "34"].
michael@0 5114 result = range.match(/^(\d+)(?:-(\d+))?$/);
michael@0 5115 if (!result) {
michael@0 5116 throw "Invalid format";
michael@0 5117 }
michael@0 5118
michael@0 5119 from = parseInt(result[1], 10);
michael@0 5120 to = (result[2]) ? parseInt(result[2], 10) + 1 : from + 1;
michael@0 5121 if (!this._checkCellBroadcastMMISettable(from, to)) {
michael@0 5122 throw "Invalid range";
michael@0 5123 }
michael@0 5124
michael@0 5125 if (list == null) {
michael@0 5126 list = [];
michael@0 5127 }
michael@0 5128 list.push(from);
michael@0 5129 list.push(to);
michael@0 5130 }
michael@0 5131
michael@0 5132 return list;
michael@0 5133 },
michael@0 5134
michael@0 5135 /**
michael@0 5136 * Handle incoming messages from the main UI thread.
michael@0 5137 *
michael@0 5138 * @param message
michael@0 5139 * Object containing the message. Messages are supposed
michael@0 5140 */
michael@0 5141 handleChromeMessage: function(message) {
michael@0 5142 if (DEBUG) {
michael@0 5143 this.context.debug("Received chrome message " + JSON.stringify(message));
michael@0 5144 }
michael@0 5145 let method = this[message.rilMessageType];
michael@0 5146 if (typeof method != "function") {
michael@0 5147 if (DEBUG) {
michael@0 5148 this.context.debug("Don't know what to do with message " +
michael@0 5149 JSON.stringify(message));
michael@0 5150 }
michael@0 5151 return;
michael@0 5152 }
michael@0 5153 method.call(this, message);
michael@0 5154 },
michael@0 5155
michael@0 5156 /**
michael@0 5157 * Get a list of current voice calls.
michael@0 5158 */
michael@0 5159 enumerateCalls: function(options) {
michael@0 5160 if (DEBUG) this.context.debug("Sending all current calls");
michael@0 5161 let calls = [];
michael@0 5162 for each (let call in this.currentCalls) {
michael@0 5163 calls.push(call);
michael@0 5164 }
michael@0 5165 options.calls = calls;
michael@0 5166 this.sendChromeMessage(options);
michael@0 5167 },
michael@0 5168
michael@0 5169 /**
michael@0 5170 * Process STK Proactive Command.
michael@0 5171 */
michael@0 5172 processStkProactiveCommand: function() {
michael@0 5173 let Buf = this.context.Buf;
michael@0 5174 let length = Buf.readInt32();
michael@0 5175 let berTlv;
michael@0 5176 try {
michael@0 5177 berTlv = this.context.BerTlvHelper.decode(length / 2);
michael@0 5178 } catch (e) {
michael@0 5179 if (DEBUG) this.context.debug("processStkProactiveCommand : " + e);
michael@0 5180 this.sendStkTerminalResponse({
michael@0 5181 resultCode: STK_RESULT_CMD_DATA_NOT_UNDERSTOOD});
michael@0 5182 return;
michael@0 5183 }
michael@0 5184
michael@0 5185 Buf.readStringDelimiter(length);
michael@0 5186
michael@0 5187 let ctlvs = berTlv.value;
michael@0 5188 let ctlv = this.context.StkProactiveCmdHelper.searchForTag(
michael@0 5189 COMPREHENSIONTLV_TAG_COMMAND_DETAILS, ctlvs);
michael@0 5190 if (!ctlv) {
michael@0 5191 this.sendStkTerminalResponse({
michael@0 5192 resultCode: STK_RESULT_CMD_DATA_NOT_UNDERSTOOD});
michael@0 5193 throw new Error("Can't find COMMAND_DETAILS ComprehensionTlv");
michael@0 5194 }
michael@0 5195
michael@0 5196 let cmdDetails = ctlv.value;
michael@0 5197 if (DEBUG) {
michael@0 5198 this.context.debug("commandNumber = " + cmdDetails.commandNumber +
michael@0 5199 " typeOfCommand = " + cmdDetails.typeOfCommand.toString(16) +
michael@0 5200 " commandQualifier = " + cmdDetails.commandQualifier);
michael@0 5201 }
michael@0 5202
michael@0 5203 // STK_CMD_MORE_TIME need not to propagate event to chrome.
michael@0 5204 if (cmdDetails.typeOfCommand == STK_CMD_MORE_TIME) {
michael@0 5205 this.sendStkTerminalResponse({
michael@0 5206 command: cmdDetails,
michael@0 5207 resultCode: STK_RESULT_OK});
michael@0 5208 return;
michael@0 5209 }
michael@0 5210
michael@0 5211 cmdDetails.rilMessageType = "stkcommand";
michael@0 5212 cmdDetails.options =
michael@0 5213 this.context.StkCommandParamsFactory.createParam(cmdDetails, ctlvs);
michael@0 5214 this.sendChromeMessage(cmdDetails);
michael@0 5215 },
michael@0 5216
michael@0 5217 /**
michael@0 5218 * Send messages to the main thread.
michael@0 5219 */
michael@0 5220 sendChromeMessage: function(message) {
michael@0 5221 message.rilMessageClientId = this.context.clientId;
michael@0 5222 postMessage(message);
michael@0 5223 },
michael@0 5224
michael@0 5225 /**
michael@0 5226 * Handle incoming requests from the RIL. We find the method that
michael@0 5227 * corresponds to the request type. Incidentally, the request type
michael@0 5228 * _is_ the method name, so that's easy.
michael@0 5229 */
michael@0 5230
michael@0 5231 handleParcel: function(request_type, length, options) {
michael@0 5232 let method = this[request_type];
michael@0 5233 if (typeof method == "function") {
michael@0 5234 if (DEBUG) this.context.debug("Handling parcel as " + method.name);
michael@0 5235 method.call(this, length, options);
michael@0 5236 }
michael@0 5237 }
michael@0 5238 };
michael@0 5239
michael@0 5240 RilObject.prototype[REQUEST_GET_SIM_STATUS] = function REQUEST_GET_SIM_STATUS(length, options) {
michael@0 5241 if (options.rilRequestError) {
michael@0 5242 return;
michael@0 5243 }
michael@0 5244
michael@0 5245 let iccStatus = {};
michael@0 5246 let Buf = this.context.Buf;
michael@0 5247 iccStatus.cardState = Buf.readInt32(); // CARD_STATE_*
michael@0 5248 iccStatus.universalPINState = Buf.readInt32(); // CARD_PINSTATE_*
michael@0 5249 iccStatus.gsmUmtsSubscriptionAppIndex = Buf.readInt32();
michael@0 5250 iccStatus.cdmaSubscriptionAppIndex = Buf.readInt32();
michael@0 5251 if (!this.v5Legacy) {
michael@0 5252 iccStatus.imsSubscriptionAppIndex = Buf.readInt32();
michael@0 5253 }
michael@0 5254
michael@0 5255 let apps_length = Buf.readInt32();
michael@0 5256 if (apps_length > CARD_MAX_APPS) {
michael@0 5257 apps_length = CARD_MAX_APPS;
michael@0 5258 }
michael@0 5259
michael@0 5260 iccStatus.apps = [];
michael@0 5261 for (let i = 0 ; i < apps_length ; i++) {
michael@0 5262 iccStatus.apps.push({
michael@0 5263 app_type: Buf.readInt32(), // CARD_APPTYPE_*
michael@0 5264 app_state: Buf.readInt32(), // CARD_APPSTATE_*
michael@0 5265 perso_substate: Buf.readInt32(), // CARD_PERSOSUBSTATE_*
michael@0 5266 aid: Buf.readString(),
michael@0 5267 app_label: Buf.readString(),
michael@0 5268 pin1_replaced: Buf.readInt32(),
michael@0 5269 pin1: Buf.readInt32(),
michael@0 5270 pin2: Buf.readInt32()
michael@0 5271 });
michael@0 5272 if (RILQUIRKS_SIM_APP_STATE_EXTRA_FIELDS) {
michael@0 5273 Buf.readInt32();
michael@0 5274 Buf.readInt32();
michael@0 5275 Buf.readInt32();
michael@0 5276 Buf.readInt32();
michael@0 5277 }
michael@0 5278 }
michael@0 5279
michael@0 5280 if (DEBUG) this.context.debug("iccStatus: " + JSON.stringify(iccStatus));
michael@0 5281 this._processICCStatus(iccStatus);
michael@0 5282 };
michael@0 5283 RilObject.prototype[REQUEST_ENTER_SIM_PIN] = function REQUEST_ENTER_SIM_PIN(length, options) {
michael@0 5284 this._processEnterAndChangeICCResponses(length, options);
michael@0 5285 };
michael@0 5286 RilObject.prototype[REQUEST_ENTER_SIM_PUK] = function REQUEST_ENTER_SIM_PUK(length, options) {
michael@0 5287 this._processEnterAndChangeICCResponses(length, options);
michael@0 5288 };
michael@0 5289 RilObject.prototype[REQUEST_ENTER_SIM_PIN2] = function REQUEST_ENTER_SIM_PIN2(length, options) {
michael@0 5290 this._processEnterAndChangeICCResponses(length, options);
michael@0 5291 };
michael@0 5292 RilObject.prototype[REQUEST_ENTER_SIM_PUK2] = function REQUEST_ENTER_SIM_PUK(length, options) {
michael@0 5293 this._processEnterAndChangeICCResponses(length, options);
michael@0 5294 };
michael@0 5295 RilObject.prototype[REQUEST_CHANGE_SIM_PIN] = function REQUEST_CHANGE_SIM_PIN(length, options) {
michael@0 5296 this._processEnterAndChangeICCResponses(length, options);
michael@0 5297 };
michael@0 5298 RilObject.prototype[REQUEST_CHANGE_SIM_PIN2] = function REQUEST_CHANGE_SIM_PIN2(length, options) {
michael@0 5299 this._processEnterAndChangeICCResponses(length, options);
michael@0 5300 };
michael@0 5301 RilObject.prototype[REQUEST_ENTER_NETWORK_DEPERSONALIZATION_CODE] =
michael@0 5302 function REQUEST_ENTER_NETWORK_DEPERSONALIZATION_CODE(length, options) {
michael@0 5303 this._processEnterAndChangeICCResponses(length, options);
michael@0 5304 };
michael@0 5305 RilObject.prototype[REQUEST_GET_CURRENT_CALLS] = function REQUEST_GET_CURRENT_CALLS(length, options) {
michael@0 5306 if (options.rilRequestError) {
michael@0 5307 return;
michael@0 5308 }
michael@0 5309
michael@0 5310 let Buf = this.context.Buf;
michael@0 5311 let calls_length = 0;
michael@0 5312 // The RIL won't even send us the length integer if there are no active calls.
michael@0 5313 // So only read this integer if the parcel actually has it.
michael@0 5314 if (length) {
michael@0 5315 calls_length = Buf.readInt32();
michael@0 5316 }
michael@0 5317 if (!calls_length) {
michael@0 5318 this._processCalls(null);
michael@0 5319 return;
michael@0 5320 }
michael@0 5321
michael@0 5322 let calls = {};
michael@0 5323 for (let i = 0; i < calls_length; i++) {
michael@0 5324 let call = {};
michael@0 5325
michael@0 5326 // Extra uint32 field to get correct callIndex and rest of call data for
michael@0 5327 // call waiting feature.
michael@0 5328 if (RILQUIRKS_EXTRA_UINT32_2ND_CALL && i > 0) {
michael@0 5329 Buf.readInt32();
michael@0 5330 }
michael@0 5331
michael@0 5332 call.state = Buf.readInt32(); // CALL_STATE_*
michael@0 5333 call.callIndex = Buf.readInt32(); // GSM index (1-based)
michael@0 5334 call.toa = Buf.readInt32();
michael@0 5335 call.isMpty = Boolean(Buf.readInt32());
michael@0 5336 call.isMT = Boolean(Buf.readInt32());
michael@0 5337 call.als = Buf.readInt32();
michael@0 5338 call.isVoice = Boolean(Buf.readInt32());
michael@0 5339 call.isVoicePrivacy = Boolean(Buf.readInt32());
michael@0 5340 if (RILQUIRKS_CALLSTATE_EXTRA_UINT32) {
michael@0 5341 Buf.readInt32();
michael@0 5342 }
michael@0 5343 call.number = Buf.readString(); //TODO munge with TOA
michael@0 5344 call.numberPresentation = Buf.readInt32(); // CALL_PRESENTATION_*
michael@0 5345 call.name = Buf.readString();
michael@0 5346 call.namePresentation = Buf.readInt32();
michael@0 5347
michael@0 5348 call.uusInfo = null;
michael@0 5349 let uusInfoPresent = Buf.readInt32();
michael@0 5350 if (uusInfoPresent == 1) {
michael@0 5351 call.uusInfo = {
michael@0 5352 type: Buf.readInt32(),
michael@0 5353 dcs: Buf.readInt32(),
michael@0 5354 userData: null //XXX TODO byte array?!?
michael@0 5355 };
michael@0 5356 }
michael@0 5357
michael@0 5358 calls[call.callIndex] = call;
michael@0 5359 }
michael@0 5360 this._processCalls(calls);
michael@0 5361 };
michael@0 5362 RilObject.prototype[REQUEST_DIAL] = function REQUEST_DIAL(length, options) {
michael@0 5363 // We already return a successful response before. Don't respond it again!
michael@0 5364 if (options.rilRequestError) {
michael@0 5365 this.getFailCauseCode((function(failCause) {
michael@0 5366 this._removePendingOutgoingCall(failCause);
michael@0 5367 }).bind(this));
michael@0 5368 }
michael@0 5369 };
michael@0 5370 RilObject.prototype[REQUEST_DIAL_EMERGENCY_CALL] = RilObject.prototype[REQUEST_DIAL];
michael@0 5371 RilObject.prototype[REQUEST_GET_IMSI] = function REQUEST_GET_IMSI(length, options) {
michael@0 5372 if (options.rilRequestError) {
michael@0 5373 return;
michael@0 5374 }
michael@0 5375
michael@0 5376 this.iccInfoPrivate.imsi = this.context.Buf.readString();
michael@0 5377 if (DEBUG) {
michael@0 5378 this.context.debug("IMSI: " + this.iccInfoPrivate.imsi);
michael@0 5379 }
michael@0 5380
michael@0 5381 options.rilMessageType = "iccimsi";
michael@0 5382 options.imsi = this.iccInfoPrivate.imsi;
michael@0 5383 this.sendChromeMessage(options);
michael@0 5384 };
michael@0 5385 RilObject.prototype[REQUEST_HANGUP] = function REQUEST_HANGUP(length, options) {
michael@0 5386 if (options.rilRequestError) {
michael@0 5387 return;
michael@0 5388 }
michael@0 5389
michael@0 5390 this.getCurrentCalls();
michael@0 5391 };
michael@0 5392 RilObject.prototype[REQUEST_HANGUP_WAITING_OR_BACKGROUND] = function REQUEST_HANGUP_WAITING_OR_BACKGROUND(length, options) {
michael@0 5393 if (options.rilRequestError) {
michael@0 5394 return;
michael@0 5395 }
michael@0 5396
michael@0 5397 this.getCurrentCalls();
michael@0 5398 };
michael@0 5399 RilObject.prototype[REQUEST_HANGUP_FOREGROUND_RESUME_BACKGROUND] = function REQUEST_HANGUP_FOREGROUND_RESUME_BACKGROUND(length, options) {
michael@0 5400 if (options.rilRequestError) {
michael@0 5401 return;
michael@0 5402 }
michael@0 5403
michael@0 5404 this.getCurrentCalls();
michael@0 5405 };
michael@0 5406 RilObject.prototype[REQUEST_SWITCH_WAITING_OR_HOLDING_AND_ACTIVE] = function REQUEST_SWITCH_WAITING_OR_HOLDING_AND_ACTIVE(length, options) {
michael@0 5407 options.success = (options.rilRequestError === 0);
michael@0 5408 if (!options.success) {
michael@0 5409 options.errorMsg = RIL_ERROR_TO_GECKO_ERROR[options.rilRequestError];
michael@0 5410 this.sendChromeMessage(options);
michael@0 5411 return;
michael@0 5412 }
michael@0 5413
michael@0 5414 this.sendChromeMessage(options);
michael@0 5415 this.getCurrentCalls();
michael@0 5416 };
michael@0 5417 RilObject.prototype[REQUEST_CONFERENCE] = function REQUEST_CONFERENCE(length, options) {
michael@0 5418 options.success = (options.rilRequestError === 0);
michael@0 5419 if (!options.success) {
michael@0 5420 this._hasConferenceRequest = false;
michael@0 5421 options.errorName = "addError";
michael@0 5422 options.errorMsg = RIL_ERROR_TO_GECKO_ERROR[options.rilRequestError];
michael@0 5423 this.sendChromeMessage(options);
michael@0 5424 return;
michael@0 5425 }
michael@0 5426
michael@0 5427 this.sendChromeMessage(options);
michael@0 5428 };
michael@0 5429 RilObject.prototype[REQUEST_UDUB] = null;
michael@0 5430 RilObject.prototype[REQUEST_LAST_CALL_FAIL_CAUSE] = function REQUEST_LAST_CALL_FAIL_CAUSE(length, options) {
michael@0 5431 let Buf = this.context.Buf;
michael@0 5432 let num = length ? Buf.readInt32() : 0;
michael@0 5433 let failCause = num ? RIL_CALL_FAILCAUSE_TO_GECKO_CALL_ERROR[Buf.readInt32()] : null;
michael@0 5434 if (options.callback) {
michael@0 5435 options.callback(failCause);
michael@0 5436 }
michael@0 5437 };
michael@0 5438 RilObject.prototype[REQUEST_SIGNAL_STRENGTH] = function REQUEST_SIGNAL_STRENGTH(length, options) {
michael@0 5439 this._receivedNetworkInfo(NETWORK_INFO_SIGNAL);
michael@0 5440
michael@0 5441 if (options.rilRequestError) {
michael@0 5442 return;
michael@0 5443 }
michael@0 5444
michael@0 5445 let Buf = this.context.Buf;
michael@0 5446 let signal = {
michael@0 5447 gsmSignalStrength: Buf.readInt32(),
michael@0 5448 gsmBitErrorRate: Buf.readInt32(),
michael@0 5449 cdmaDBM: Buf.readInt32(),
michael@0 5450 cdmaECIO: Buf.readInt32(),
michael@0 5451 evdoDBM: Buf.readInt32(),
michael@0 5452 evdoECIO: Buf.readInt32(),
michael@0 5453 evdoSNR: Buf.readInt32()
michael@0 5454 };
michael@0 5455
michael@0 5456 if (!this.v5Legacy) {
michael@0 5457 signal.lteSignalStrength = Buf.readInt32();
michael@0 5458 signal.lteRSRP = Buf.readInt32();
michael@0 5459 signal.lteRSRQ = Buf.readInt32();
michael@0 5460 signal.lteRSSNR = Buf.readInt32();
michael@0 5461 signal.lteCQI = Buf.readInt32();
michael@0 5462 }
michael@0 5463
michael@0 5464 if (DEBUG) this.context.debug("signal strength: " + JSON.stringify(signal));
michael@0 5465
michael@0 5466 this._processSignalStrength(signal);
michael@0 5467 };
michael@0 5468 RilObject.prototype[REQUEST_VOICE_REGISTRATION_STATE] = function REQUEST_VOICE_REGISTRATION_STATE(length, options) {
michael@0 5469 this._receivedNetworkInfo(NETWORK_INFO_VOICE_REGISTRATION_STATE);
michael@0 5470
michael@0 5471 if (options.rilRequestError) {
michael@0 5472 return;
michael@0 5473 }
michael@0 5474
michael@0 5475 let state = this.context.Buf.readStringList();
michael@0 5476 if (DEBUG) this.context.debug("voice registration state: " + state);
michael@0 5477
michael@0 5478 this._processVoiceRegistrationState(state);
michael@0 5479
michael@0 5480 if (this.cachedDialRequest &&
michael@0 5481 (this.voiceRegistrationState.emergencyCallsOnly ||
michael@0 5482 this.voiceRegistrationState.connected) &&
michael@0 5483 this.voiceRegistrationState.radioTech != NETWORK_CREG_TECH_UNKNOWN) {
michael@0 5484 // Radio is ready for making the cached emergency call.
michael@0 5485 this.cachedDialRequest.callback();
michael@0 5486 this.cachedDialRequest = null;
michael@0 5487 }
michael@0 5488 };
michael@0 5489 RilObject.prototype[REQUEST_DATA_REGISTRATION_STATE] = function REQUEST_DATA_REGISTRATION_STATE(length, options) {
michael@0 5490 this._receivedNetworkInfo(NETWORK_INFO_DATA_REGISTRATION_STATE);
michael@0 5491
michael@0 5492 if (options.rilRequestError) {
michael@0 5493 return;
michael@0 5494 }
michael@0 5495
michael@0 5496 let state = this.context.Buf.readStringList();
michael@0 5497 this._processDataRegistrationState(state);
michael@0 5498 };
michael@0 5499 RilObject.prototype[REQUEST_OPERATOR] = function REQUEST_OPERATOR(length, options) {
michael@0 5500 this._receivedNetworkInfo(NETWORK_INFO_OPERATOR);
michael@0 5501
michael@0 5502 if (options.rilRequestError) {
michael@0 5503 return;
michael@0 5504 }
michael@0 5505
michael@0 5506 let operatorData = this.context.Buf.readStringList();
michael@0 5507 if (DEBUG) this.context.debug("Operator: " + operatorData);
michael@0 5508 this._processOperator(operatorData);
michael@0 5509 };
michael@0 5510 RilObject.prototype[REQUEST_RADIO_POWER] = function REQUEST_RADIO_POWER(length, options) {
michael@0 5511 if (options.rilMessageType == null) {
michael@0 5512 // The request was made by ril_worker itself.
michael@0 5513 if (options.rilRequestError) {
michael@0 5514 if (this.cachedDialRequest && options.enabled) {
michael@0 5515 // Turning on radio fails. Notify the error of making an emergency call.
michael@0 5516 this.cachedDialRequest.onerror(GECKO_ERROR_RADIO_NOT_AVAILABLE);
michael@0 5517 this.cachedDialRequest = null;
michael@0 5518 }
michael@0 5519 }
michael@0 5520 return;
michael@0 5521 }
michael@0 5522
michael@0 5523 options.errorMsg = RIL_ERROR_TO_GECKO_ERROR[options.rilRequestError];
michael@0 5524 this.sendChromeMessage(options);
michael@0 5525 };
michael@0 5526 RilObject.prototype[REQUEST_DTMF] = null;
michael@0 5527 RilObject.prototype[REQUEST_SEND_SMS] = function REQUEST_SEND_SMS(length, options) {
michael@0 5528 this._processSmsSendResult(length, options);
michael@0 5529 };
michael@0 5530 RilObject.prototype[REQUEST_SEND_SMS_EXPECT_MORE] = null;
michael@0 5531
michael@0 5532 RilObject.prototype.readSetupDataCall_v5 = function readSetupDataCall_v5(options) {
michael@0 5533 if (!options) {
michael@0 5534 options = {};
michael@0 5535 }
michael@0 5536 let [cid, ifname, addresses, dnses, gateways] = this.context.Buf.readStringList();
michael@0 5537 options.cid = cid;
michael@0 5538 options.ifname = ifname;
michael@0 5539 options.addresses = addresses ? [addresses] : [];
michael@0 5540 options.dnses = dnses ? [dnses] : [];
michael@0 5541 options.gateways = gateways ? [gateways] : [];
michael@0 5542 options.active = DATACALL_ACTIVE_UNKNOWN;
michael@0 5543 options.state = GECKO_NETWORK_STATE_CONNECTING;
michael@0 5544 return options;
michael@0 5545 };
michael@0 5546
michael@0 5547 RilObject.prototype[REQUEST_SETUP_DATA_CALL] = function REQUEST_SETUP_DATA_CALL(length, options) {
michael@0 5548 if (options.rilRequestError) {
michael@0 5549 // On Data Call generic errors, we shall notify caller
michael@0 5550 this._sendDataCallError(options, options.rilRequestError);
michael@0 5551 return;
michael@0 5552 }
michael@0 5553
michael@0 5554 if (this.v5Legacy) {
michael@0 5555 // Populate the `options` object with the data call information. That way
michael@0 5556 // we retain the APN and other info about how the data call was set up.
michael@0 5557 this.readSetupDataCall_v5(options);
michael@0 5558 this.currentDataCalls[options.cid] = options;
michael@0 5559 options.rilMessageType = "datacallstatechange";
michael@0 5560 this.sendChromeMessage(options);
michael@0 5561 // Let's get the list of data calls to ensure we know whether it's active
michael@0 5562 // or not.
michael@0 5563 this.getDataCallList();
michael@0 5564 return;
michael@0 5565 }
michael@0 5566 // Pass `options` along. That way we retain the APN and other info about
michael@0 5567 // how the data call was set up.
michael@0 5568 this[REQUEST_DATA_CALL_LIST](length, options);
michael@0 5569 };
michael@0 5570 RilObject.prototype[REQUEST_SIM_IO] = function REQUEST_SIM_IO(length, options) {
michael@0 5571 let ICCIOHelper = this.context.ICCIOHelper;
michael@0 5572 if (!length) {
michael@0 5573 ICCIOHelper.processICCIOError(options);
michael@0 5574 return;
michael@0 5575 }
michael@0 5576
michael@0 5577 // Don't need to read rilRequestError since we can know error status from
michael@0 5578 // sw1 and sw2.
michael@0 5579 let Buf = this.context.Buf;
michael@0 5580 options.sw1 = Buf.readInt32();
michael@0 5581 options.sw2 = Buf.readInt32();
michael@0 5582 if (options.sw1 != ICC_STATUS_NORMAL_ENDING) {
michael@0 5583 ICCIOHelper.processICCIOError(options);
michael@0 5584 return;
michael@0 5585 }
michael@0 5586 ICCIOHelper.processICCIO(options);
michael@0 5587 };
michael@0 5588 RilObject.prototype[REQUEST_SEND_USSD] = function REQUEST_SEND_USSD(length, options) {
michael@0 5589 if (DEBUG) {
michael@0 5590 this.context.debug("REQUEST_SEND_USSD " + JSON.stringify(options));
michael@0 5591 }
michael@0 5592 options.success = (this._ussdSession = options.rilRequestError === 0);
michael@0 5593 options.errorMsg = RIL_ERROR_TO_GECKO_ERROR[options.rilRequestError];
michael@0 5594 this.sendChromeMessage(options);
michael@0 5595 };
michael@0 5596 RilObject.prototype[REQUEST_CANCEL_USSD] = function REQUEST_CANCEL_USSD(length, options) {
michael@0 5597 if (DEBUG) {
michael@0 5598 this.context.debug("REQUEST_CANCEL_USSD" + JSON.stringify(options));
michael@0 5599 }
michael@0 5600 options.success = (options.rilRequestError === 0);
michael@0 5601 this._ussdSession = !options.success;
michael@0 5602 options.errorMsg = RIL_ERROR_TO_GECKO_ERROR[options.rilRequestError];
michael@0 5603 this.sendChromeMessage(options);
michael@0 5604 };
michael@0 5605 RilObject.prototype[REQUEST_GET_CLIR] = function REQUEST_GET_CLIR(length, options) {
michael@0 5606 options.success = (options.rilRequestError === 0);
michael@0 5607 if (!options.success) {
michael@0 5608 options.errorMsg = RIL_ERROR_TO_GECKO_ERROR[options.rilRequestError];
michael@0 5609 this.sendChromeMessage(options);
michael@0 5610 return;
michael@0 5611 }
michael@0 5612
michael@0 5613 let Buf = this.context.Buf;
michael@0 5614 let bufLength = Buf.readInt32();
michael@0 5615 if (!bufLength || bufLength < 2) {
michael@0 5616 options.success = false;
michael@0 5617 options.errorMsg = GECKO_ERROR_GENERIC_FAILURE;
michael@0 5618 this.sendChromeMessage(options);
michael@0 5619 return;
michael@0 5620 }
michael@0 5621
michael@0 5622 options.n = Buf.readInt32(); // Will be TS 27.007 +CLIR parameter 'n'.
michael@0 5623 options.m = Buf.readInt32(); // Will be TS 27.007 +CLIR parameter 'm'.
michael@0 5624
michael@0 5625 if (options.rilMessageType === "sendMMI") {
michael@0 5626 // TS 27.007 +CLIR parameter 'm'.
michael@0 5627 switch (options.m) {
michael@0 5628 // CLIR not provisioned.
michael@0 5629 case 0:
michael@0 5630 options.statusMessage = MMI_SM_KS_SERVICE_NOT_PROVISIONED;
michael@0 5631 break;
michael@0 5632 // CLIR provisioned in permanent mode.
michael@0 5633 case 1:
michael@0 5634 options.statusMessage = MMI_SM_KS_CLIR_PERMANENT;
michael@0 5635 break;
michael@0 5636 // Unknown (e.g. no network, etc.).
michael@0 5637 case 2:
michael@0 5638 options.success = false;
michael@0 5639 options.errorMsg = MMI_ERROR_KS_ERROR;
michael@0 5640 break;
michael@0 5641 // CLIR temporary mode presentation restricted.
michael@0 5642 case 3:
michael@0 5643 // TS 27.007 +CLIR parameter 'n'.
michael@0 5644 switch (options.n) {
michael@0 5645 // Default.
michael@0 5646 case 0:
michael@0 5647 // CLIR invocation.
michael@0 5648 case 1:
michael@0 5649 options.statusMessage = MMI_SM_KS_CLIR_DEFAULT_ON_NEXT_CALL_ON;
michael@0 5650 break;
michael@0 5651 // CLIR suppression.
michael@0 5652 case 2:
michael@0 5653 options.statusMessage = MMI_SM_KS_CLIR_DEFAULT_ON_NEXT_CALL_OFF;
michael@0 5654 break;
michael@0 5655 default:
michael@0 5656 options.success = false;
michael@0 5657 options.errorMsg = GECKO_ERROR_GENERIC_FAILURE;
michael@0 5658 break;
michael@0 5659 }
michael@0 5660 break;
michael@0 5661 // CLIR temporary mode presentation allowed.
michael@0 5662 case 4:
michael@0 5663 // TS 27.007 +CLIR parameter 'n'.
michael@0 5664 switch (options.n) {
michael@0 5665 // Default.
michael@0 5666 case 0:
michael@0 5667 // CLIR suppression.
michael@0 5668 case 2:
michael@0 5669 options.statusMessage = MMI_SM_KS_CLIR_DEFAULT_OFF_NEXT_CALL_OFF;
michael@0 5670 break;
michael@0 5671 // CLIR invocation.
michael@0 5672 case 1:
michael@0 5673 options.statusMessage = MMI_SM_KS_CLIR_DEFAULT_OFF_NEXT_CALL_ON;
michael@0 5674 break;
michael@0 5675 default:
michael@0 5676 options.success = false;
michael@0 5677 options.errorMsg = GECKO_ERROR_GENERIC_FAILURE;
michael@0 5678 break;
michael@0 5679 }
michael@0 5680 break;
michael@0 5681 default:
michael@0 5682 options.success = false;
michael@0 5683 options.errorMsg = GECKO_ERROR_GENERIC_FAILURE;
michael@0 5684 break;
michael@0 5685 }
michael@0 5686 }
michael@0 5687
michael@0 5688 this.sendChromeMessage(options);
michael@0 5689 };
michael@0 5690 RilObject.prototype[REQUEST_SET_CLIR] = function REQUEST_SET_CLIR(length, options) {
michael@0 5691 if (options.rilMessageType == null) {
michael@0 5692 // The request was made by ril_worker itself automatically. Don't report.
michael@0 5693 return;
michael@0 5694 }
michael@0 5695 options.success = (options.rilRequestError === 0);
michael@0 5696 if (!options.success) {
michael@0 5697 options.errorMsg = RIL_ERROR_TO_GECKO_ERROR[options.rilRequestError];
michael@0 5698 } else if (options.rilMessageType === "sendMMI") {
michael@0 5699 switch (options.procedure) {
michael@0 5700 case MMI_PROCEDURE_ACTIVATION:
michael@0 5701 options.statusMessage = MMI_SM_KS_SERVICE_ENABLED;
michael@0 5702 break;
michael@0 5703 case MMI_PROCEDURE_DEACTIVATION:
michael@0 5704 options.statusMessage = MMI_SM_KS_SERVICE_DISABLED;
michael@0 5705 break;
michael@0 5706 }
michael@0 5707 }
michael@0 5708 this.sendChromeMessage(options);
michael@0 5709 };
michael@0 5710
michael@0 5711 RilObject.prototype[REQUEST_QUERY_CALL_FORWARD_STATUS] =
michael@0 5712 function REQUEST_QUERY_CALL_FORWARD_STATUS(length, options) {
michael@0 5713 options.success = (options.rilRequestError === 0);
michael@0 5714 if (!options.success) {
michael@0 5715 options.errorMsg = RIL_ERROR_TO_GECKO_ERROR[options.rilRequestError];
michael@0 5716 this.sendChromeMessage(options);
michael@0 5717 return;
michael@0 5718 }
michael@0 5719
michael@0 5720 let Buf = this.context.Buf;
michael@0 5721 let rulesLength = 0;
michael@0 5722 if (length) {
michael@0 5723 rulesLength = Buf.readInt32();
michael@0 5724 }
michael@0 5725 if (!rulesLength) {
michael@0 5726 options.success = false;
michael@0 5727 options.errorMsg = GECKO_ERROR_GENERIC_FAILURE;
michael@0 5728 this.sendChromeMessage(options);
michael@0 5729 return;
michael@0 5730 }
michael@0 5731 let rules = new Array(rulesLength);
michael@0 5732 for (let i = 0; i < rulesLength; i++) {
michael@0 5733 let rule = {};
michael@0 5734 rule.active = Buf.readInt32() == 1; // CALL_FORWARD_STATUS_*
michael@0 5735 rule.reason = Buf.readInt32(); // CALL_FORWARD_REASON_*
michael@0 5736 rule.serviceClass = Buf.readInt32();
michael@0 5737 rule.toa = Buf.readInt32();
michael@0 5738 rule.number = Buf.readString();
michael@0 5739 rule.timeSeconds = Buf.readInt32();
michael@0 5740 rules[i] = rule;
michael@0 5741 }
michael@0 5742 options.rules = rules;
michael@0 5743 if (options.rilMessageType === "sendMMI") {
michael@0 5744 options.statusMessage = MMI_SM_KS_SERVICE_INTERROGATED;
michael@0 5745 // MMI query call forwarding options request returns a set of rules that
michael@0 5746 // will be exposed in the form of an array of nsIDOMMozMobileCFInfo
michael@0 5747 // instances.
michael@0 5748 options.additionalInformation = rules;
michael@0 5749 }
michael@0 5750 this.sendChromeMessage(options);
michael@0 5751 };
michael@0 5752 RilObject.prototype[REQUEST_SET_CALL_FORWARD] =
michael@0 5753 function REQUEST_SET_CALL_FORWARD(length, options) {
michael@0 5754 options.success = (options.rilRequestError === 0);
michael@0 5755 if (!options.success) {
michael@0 5756 options.errorMsg = RIL_ERROR_TO_GECKO_ERROR[options.rilRequestError];
michael@0 5757 } else if (options.rilMessageType === "sendMMI") {
michael@0 5758 switch (options.action) {
michael@0 5759 case CALL_FORWARD_ACTION_ENABLE:
michael@0 5760 options.statusMessage = MMI_SM_KS_SERVICE_ENABLED;
michael@0 5761 break;
michael@0 5762 case CALL_FORWARD_ACTION_DISABLE:
michael@0 5763 options.statusMessage = MMI_SM_KS_SERVICE_DISABLED;
michael@0 5764 break;
michael@0 5765 case CALL_FORWARD_ACTION_REGISTRATION:
michael@0 5766 options.statusMessage = MMI_SM_KS_SERVICE_REGISTERED;
michael@0 5767 break;
michael@0 5768 case CALL_FORWARD_ACTION_ERASURE:
michael@0 5769 options.statusMessage = MMI_SM_KS_SERVICE_ERASED;
michael@0 5770 break;
michael@0 5771 }
michael@0 5772 }
michael@0 5773 this.sendChromeMessage(options);
michael@0 5774 };
michael@0 5775 RilObject.prototype[REQUEST_QUERY_CALL_WAITING] =
michael@0 5776 function REQUEST_QUERY_CALL_WAITING(length, options) {
michael@0 5777 options.success = (options.rilRequestError === 0);
michael@0 5778 if (!options.success) {
michael@0 5779 options.errorMsg = RIL_ERROR_TO_GECKO_ERROR[options.rilRequestError];
michael@0 5780 this.sendChromeMessage(options);
michael@0 5781 return;
michael@0 5782 }
michael@0 5783
michael@0 5784 if (options.callback) {
michael@0 5785 options.callback.call(this, options);
michael@0 5786 return;
michael@0 5787 }
michael@0 5788
michael@0 5789 let Buf = this.context.Buf;
michael@0 5790 options.length = Buf.readInt32();
michael@0 5791 options.enabled = ((Buf.readInt32() == 1) &&
michael@0 5792 ((Buf.readInt32() & ICC_SERVICE_CLASS_VOICE) == 0x01));
michael@0 5793 this.sendChromeMessage(options);
michael@0 5794 };
michael@0 5795
michael@0 5796 RilObject.prototype[REQUEST_SET_CALL_WAITING] = function REQUEST_SET_CALL_WAITING(length, options) {
michael@0 5797 options.success = (options.rilRequestError === 0);
michael@0 5798 if (!options.success) {
michael@0 5799 options.errorMsg = RIL_ERROR_TO_GECKO_ERROR[options.rilRequestError];
michael@0 5800 this.sendChromeMessage(options);
michael@0 5801 return;
michael@0 5802 }
michael@0 5803
michael@0 5804 if (options.callback) {
michael@0 5805 options.callback.call(this, options);
michael@0 5806 return;
michael@0 5807 }
michael@0 5808
michael@0 5809 this.sendChromeMessage(options);
michael@0 5810 };
michael@0 5811 RilObject.prototype[REQUEST_SMS_ACKNOWLEDGE] = null;
michael@0 5812 RilObject.prototype[REQUEST_GET_IMEI] = function REQUEST_GET_IMEI(length, options) {
michael@0 5813 this.IMEI = this.context.Buf.readString();
michael@0 5814 let rilMessageType = options.rilMessageType;
michael@0 5815 // So far we only send the IMEI back to chrome if it was requested via MMI.
michael@0 5816 if (rilMessageType !== "sendMMI") {
michael@0 5817 return;
michael@0 5818 }
michael@0 5819
michael@0 5820 options.success = (options.rilRequestError === 0);
michael@0 5821 options.errorMsg = RIL_ERROR_TO_GECKO_ERROR[options.rilRequestError];
michael@0 5822 if ((!options.success || this.IMEI == null) && !options.errorMsg) {
michael@0 5823 options.errorMsg = GECKO_ERROR_GENERIC_FAILURE;
michael@0 5824 }
michael@0 5825 options.statusMessage = this.IMEI;
michael@0 5826 this.sendChromeMessage(options);
michael@0 5827 };
michael@0 5828 RilObject.prototype[REQUEST_GET_IMEISV] = function REQUEST_GET_IMEISV(length, options) {
michael@0 5829 if (options.rilRequestError) {
michael@0 5830 return;
michael@0 5831 }
michael@0 5832
michael@0 5833 this.IMEISV = this.context.Buf.readString();
michael@0 5834 };
michael@0 5835 RilObject.prototype[REQUEST_ANSWER] = null;
michael@0 5836 RilObject.prototype[REQUEST_DEACTIVATE_DATA_CALL] = function REQUEST_DEACTIVATE_DATA_CALL(length, options) {
michael@0 5837 if (options.rilRequestError) {
michael@0 5838 return;
michael@0 5839 }
michael@0 5840
michael@0 5841 let datacall = this.currentDataCalls[options.cid];
michael@0 5842 delete this.currentDataCalls[options.cid];
michael@0 5843 datacall.state = GECKO_NETWORK_STATE_UNKNOWN;
michael@0 5844 datacall.rilMessageType = "datacallstatechange";
michael@0 5845 this.sendChromeMessage(datacall);
michael@0 5846 };
michael@0 5847 RilObject.prototype[REQUEST_QUERY_FACILITY_LOCK] = function REQUEST_QUERY_FACILITY_LOCK(length, options) {
michael@0 5848 options.success = (options.rilRequestError === 0);
michael@0 5849 if (!options.success) {
michael@0 5850 options.errorMsg = RIL_ERROR_TO_GECKO_ERROR[options.rilRequestError];
michael@0 5851 }
michael@0 5852
michael@0 5853 let services;
michael@0 5854 if (length) {
michael@0 5855 // Buf.readInt32List()[0] for Call Barring is a bit vector of services.
michael@0 5856 services = this.context.Buf.readInt32List()[0];
michael@0 5857 } else {
michael@0 5858 options.success = false;
michael@0 5859 options.errorMsg = GECKO_ERROR_GENERIC_FAILURE;
michael@0 5860 this.sendChromeMessage(options);
michael@0 5861 return;
michael@0 5862 }
michael@0 5863
michael@0 5864 options.enabled = services === 0 ? false : true;
michael@0 5865
michael@0 5866 if (options.success && (options.rilMessageType === "sendMMI")) {
michael@0 5867 if (!options.enabled) {
michael@0 5868 options.statusMessage = MMI_SM_KS_SERVICE_DISABLED;
michael@0 5869 } else {
michael@0 5870 options.statusMessage = MMI_SM_KS_SERVICE_ENABLED_FOR;
michael@0 5871 let serviceClass = [];
michael@0 5872 for (let serviceClassMask = 1;
michael@0 5873 serviceClassMask <= ICC_SERVICE_CLASS_MAX;
michael@0 5874 serviceClassMask <<= 1) {
michael@0 5875 if ((serviceClassMask & services) !== 0) {
michael@0 5876 serviceClass.push(MMI_KS_SERVICE_CLASS_MAPPING[serviceClassMask]);
michael@0 5877 }
michael@0 5878 }
michael@0 5879
michael@0 5880 options.additionalInformation = serviceClass;
michael@0 5881 }
michael@0 5882 }
michael@0 5883 this.sendChromeMessage(options);
michael@0 5884 };
michael@0 5885 RilObject.prototype[REQUEST_SET_FACILITY_LOCK] = function REQUEST_SET_FACILITY_LOCK(length, options) {
michael@0 5886 options.success = (options.rilRequestError === 0);
michael@0 5887 if (!options.success) {
michael@0 5888 options.errorMsg = RIL_ERROR_TO_GECKO_ERROR[options.rilRequestError];
michael@0 5889 }
michael@0 5890
michael@0 5891 options.retryCount = length ? this.context.Buf.readInt32List()[0] : -1;
michael@0 5892
michael@0 5893 if (options.success && (options.rilMessageType === "sendMMI")) {
michael@0 5894 switch (options.procedure) {
michael@0 5895 case MMI_PROCEDURE_ACTIVATION:
michael@0 5896 options.statusMessage = MMI_SM_KS_SERVICE_ENABLED;
michael@0 5897 break;
michael@0 5898 case MMI_PROCEDURE_DEACTIVATION:
michael@0 5899 options.statusMessage = MMI_SM_KS_SERVICE_DISABLED;
michael@0 5900 break;
michael@0 5901 }
michael@0 5902 }
michael@0 5903 this.sendChromeMessage(options);
michael@0 5904 };
michael@0 5905 RilObject.prototype[REQUEST_CHANGE_BARRING_PASSWORD] =
michael@0 5906 function REQUEST_CHANGE_BARRING_PASSWORD(length, options) {
michael@0 5907 if (options.rilRequestError) {
michael@0 5908 options.errorMsg = RIL_ERROR_TO_GECKO_ERROR[options.rilRequestError];
michael@0 5909 }
michael@0 5910 this.sendChromeMessage(options);
michael@0 5911 };
michael@0 5912 RilObject.prototype[REQUEST_SIM_OPEN_CHANNEL] = function REQUEST_SIM_OPEN_CHANNEL(length, options) {
michael@0 5913 if (options.rilRequestError) {
michael@0 5914 options.errorMsg = RIL_ERROR_TO_GECKO_ERROR[options.rilRequestError];
michael@0 5915 this.sendChromeMessage(options);
michael@0 5916 return;
michael@0 5917 }
michael@0 5918
michael@0 5919 options.channel = this.context.Buf.readInt32();
michael@0 5920 if (DEBUG) {
michael@0 5921 this.context.debug("Setting channel number in options: " + options.channel);
michael@0 5922 }
michael@0 5923 this.sendChromeMessage(options);
michael@0 5924 };
michael@0 5925 RilObject.prototype[REQUEST_SIM_CLOSE_CHANNEL] = function REQUEST_SIM_CLOSE_CHANNEL(length, options) {
michael@0 5926 if (options.rilRequestError) {
michael@0 5927 options.error = RIL_ERROR_TO_GECKO_ERROR[options.rilRequestError];
michael@0 5928 this.sendChromeMessage(options);
michael@0 5929 return;
michael@0 5930 }
michael@0 5931
michael@0 5932 // No return value
michael@0 5933 this.sendChromeMessage(options);
michael@0 5934 };
michael@0 5935 RilObject.prototype[REQUEST_SIM_ACCESS_CHANNEL] = function REQUEST_SIM_ACCESS_CHANNEL(length, options) {
michael@0 5936 if (options.rilRequestError) {
michael@0 5937 options.error = RIL_ERROR_TO_GECKO_ERROR[options.rilRequestError];
michael@0 5938 this.sendChromeMessage(options);
michael@0 5939 }
michael@0 5940
michael@0 5941 let Buf = this.context.Buf;
michael@0 5942 options.sw1 = Buf.readInt32();
michael@0 5943 options.sw2 = Buf.readInt32();
michael@0 5944 options.simResponse = Buf.readString();
michael@0 5945 if (DEBUG) {
michael@0 5946 this.context.debug("Setting return values for RIL[REQUEST_SIM_ACCESS_CHANNEL]: [" +
michael@0 5947 options.sw1 + "," +
michael@0 5948 options.sw2 + ", " +
michael@0 5949 options.simResponse + "]");
michael@0 5950 }
michael@0 5951 this.sendChromeMessage(options);
michael@0 5952 };
michael@0 5953 RilObject.prototype[REQUEST_QUERY_NETWORK_SELECTION_MODE] = function REQUEST_QUERY_NETWORK_SELECTION_MODE(length, options) {
michael@0 5954 this._receivedNetworkInfo(NETWORK_INFO_NETWORK_SELECTION_MODE);
michael@0 5955
michael@0 5956 if (options.rilRequestError) {
michael@0 5957 return;
michael@0 5958 }
michael@0 5959
michael@0 5960 let mode = this.context.Buf.readInt32List();
michael@0 5961 let selectionMode;
michael@0 5962
michael@0 5963 switch (mode[0]) {
michael@0 5964 case NETWORK_SELECTION_MODE_AUTOMATIC:
michael@0 5965 selectionMode = GECKO_NETWORK_SELECTION_AUTOMATIC;
michael@0 5966 break;
michael@0 5967 case NETWORK_SELECTION_MODE_MANUAL:
michael@0 5968 selectionMode = GECKO_NETWORK_SELECTION_MANUAL;
michael@0 5969 break;
michael@0 5970 default:
michael@0 5971 selectionMode = GECKO_NETWORK_SELECTION_UNKNOWN;
michael@0 5972 break;
michael@0 5973 }
michael@0 5974
michael@0 5975 if (this.networkSelectionMode != selectionMode) {
michael@0 5976 this.networkSelectionMode = options.mode = selectionMode;
michael@0 5977 options.rilMessageType = "networkselectionmodechange";
michael@0 5978 this._sendNetworkInfoMessage(NETWORK_INFO_NETWORK_SELECTION_MODE, options);
michael@0 5979 }
michael@0 5980 };
michael@0 5981 RilObject.prototype[REQUEST_SET_NETWORK_SELECTION_AUTOMATIC] = function REQUEST_SET_NETWORK_SELECTION_AUTOMATIC(length, options) {
michael@0 5982 if (options.rilRequestError) {
michael@0 5983 options.errorMsg = RIL_ERROR_TO_GECKO_ERROR[options.rilRequestError];
michael@0 5984 }
michael@0 5985
michael@0 5986 this.sendChromeMessage(options);
michael@0 5987 };
michael@0 5988 RilObject.prototype[REQUEST_SET_NETWORK_SELECTION_MANUAL] = function REQUEST_SET_NETWORK_SELECTION_MANUAL(length, options) {
michael@0 5989 if (options.rilRequestError) {
michael@0 5990 options.errorMsg = RIL_ERROR_TO_GECKO_ERROR[options.rilRequestError];
michael@0 5991 }
michael@0 5992
michael@0 5993 this.sendChromeMessage(options);
michael@0 5994 };
michael@0 5995 RilObject.prototype[REQUEST_QUERY_AVAILABLE_NETWORKS] = function REQUEST_QUERY_AVAILABLE_NETWORKS(length, options) {
michael@0 5996 if (options.rilRequestError) {
michael@0 5997 options.errorMsg = RIL_ERROR_TO_GECKO_ERROR[options.rilRequestError];
michael@0 5998 } else {
michael@0 5999 options.networks = this._processNetworks();
michael@0 6000 }
michael@0 6001 this.sendChromeMessage(options);
michael@0 6002 };
michael@0 6003 RilObject.prototype[REQUEST_DTMF_START] = null;
michael@0 6004 RilObject.prototype[REQUEST_DTMF_STOP] = null;
michael@0 6005 RilObject.prototype[REQUEST_BASEBAND_VERSION] = function REQUEST_BASEBAND_VERSION(length, options) {
michael@0 6006 if (options.rilRequestError) {
michael@0 6007 return;
michael@0 6008 }
michael@0 6009
michael@0 6010 this.basebandVersion = this.context.Buf.readString();
michael@0 6011 if (DEBUG) this.context.debug("Baseband version: " + this.basebandVersion);
michael@0 6012 };
michael@0 6013 RilObject.prototype[REQUEST_SEPARATE_CONNECTION] = function REQUEST_SEPARATE_CONNECTION(length, options) {
michael@0 6014 options.success = (options.rilRequestError === 0);
michael@0 6015 if (!options.success) {
michael@0 6016 options.errorName = "removeError";
michael@0 6017 options.errorMsg = RIL_ERROR_TO_GECKO_ERROR[options.rilRequestError];
michael@0 6018 this.sendChromeMessage(options);
michael@0 6019 return;
michael@0 6020 }
michael@0 6021
michael@0 6022 this.sendChromeMessage(options);
michael@0 6023 };
michael@0 6024 RilObject.prototype[REQUEST_SET_MUTE] = null;
michael@0 6025 RilObject.prototype[REQUEST_GET_MUTE] = null;
michael@0 6026 RilObject.prototype[REQUEST_QUERY_CLIP] = function REQUEST_QUERY_CLIP(length, options) {
michael@0 6027 options.success = (options.rilRequestError === 0);
michael@0 6028 if (!options.success) {
michael@0 6029 options.errorMsg = RIL_ERROR_TO_GECKO_ERROR[options.rilRequestError];
michael@0 6030 this.sendChromeMessage(options);
michael@0 6031 return;
michael@0 6032 }
michael@0 6033
michael@0 6034 let Buf = this.context.Buf;
michael@0 6035 let bufLength = Buf.readInt32();
michael@0 6036 if (!bufLength) {
michael@0 6037 options.success = false;
michael@0 6038 options.errorMsg = GECKO_ERROR_GENERIC_FAILURE;
michael@0 6039 this.sendChromeMessage(options);
michael@0 6040 return;
michael@0 6041 }
michael@0 6042
michael@0 6043 // options.provisioned informs about the called party receives the calling
michael@0 6044 // party's address information:
michael@0 6045 // 0 for CLIP not provisioned
michael@0 6046 // 1 for CLIP provisioned
michael@0 6047 // 2 for unknown
michael@0 6048 options.provisioned = Buf.readInt32();
michael@0 6049 if (options.rilMessageType === "sendMMI") {
michael@0 6050 switch (options.provisioned) {
michael@0 6051 case 0:
michael@0 6052 options.statusMessage = MMI_SM_KS_SERVICE_DISABLED;
michael@0 6053 break;
michael@0 6054 case 1:
michael@0 6055 options.statusMessage = MMI_SM_KS_SERVICE_ENABLED;
michael@0 6056 break;
michael@0 6057 default:
michael@0 6058 options.success = false;
michael@0 6059 options.errorMsg = MMI_ERROR_KS_ERROR;
michael@0 6060 break;
michael@0 6061 }
michael@0 6062 }
michael@0 6063 this.sendChromeMessage(options);
michael@0 6064 };
michael@0 6065 RilObject.prototype[REQUEST_LAST_DATA_CALL_FAIL_CAUSE] = null;
michael@0 6066
michael@0 6067 /**
michael@0 6068 * V3:
michael@0 6069 * # address - A space-delimited list of addresses.
michael@0 6070 *
michael@0 6071 * V4:
michael@0 6072 * # address - An address.
michael@0 6073 *
michael@0 6074 * V5:
michael@0 6075 * # addresses - A space-delimited list of addresses.
michael@0 6076 * # dnses - A space-delimited list of DNS server addresses.
michael@0 6077 *
michael@0 6078 * V6:
michael@0 6079 * # addresses - A space-delimited list of addresses with optional "/" prefix
michael@0 6080 * length.
michael@0 6081 * # dnses - A space-delimited list of DNS server addresses.
michael@0 6082 * # gateways - A space-delimited list of default gateway addresses.
michael@0 6083 */
michael@0 6084 RilObject.prototype.readDataCall_v5 = function(options) {
michael@0 6085 if (!options) {
michael@0 6086 options = {};
michael@0 6087 }
michael@0 6088 let Buf = this.context.Buf;
michael@0 6089 options.cid = Buf.readInt32().toString();
michael@0 6090 options.active = Buf.readInt32(); // DATACALL_ACTIVE_*
michael@0 6091 options.type = Buf.readString();
michael@0 6092 options.apn = Buf.readString();
michael@0 6093 let addresses = Buf.readString();
michael@0 6094 let dnses = Buf.readString();
michael@0 6095 options.addresses = addresses ? addresses.split(" ") : [];
michael@0 6096 options.dnses = dnses ? dnses.split(" ") : [];
michael@0 6097 options.gateways = [];
michael@0 6098 return options;
michael@0 6099 };
michael@0 6100
michael@0 6101 RilObject.prototype.readDataCall_v6 = function(options) {
michael@0 6102 if (!options) {
michael@0 6103 options = {};
michael@0 6104 }
michael@0 6105 let Buf = this.context.Buf;
michael@0 6106 options.status = Buf.readInt32(); // DATACALL_FAIL_*
michael@0 6107 options.suggestedRetryTime = Buf.readInt32();
michael@0 6108 options.cid = Buf.readInt32().toString();
michael@0 6109 options.active = Buf.readInt32(); // DATACALL_ACTIVE_*
michael@0 6110 options.type = Buf.readString();
michael@0 6111 options.ifname = Buf.readString();
michael@0 6112 let addresses = Buf.readString();
michael@0 6113 let dnses = Buf.readString();
michael@0 6114 let gateways = Buf.readString();
michael@0 6115 options.addresses = addresses ? addresses.split(" ") : [];
michael@0 6116 options.dnses = dnses ? dnses.split(" ") : [];
michael@0 6117 options.gateways = gateways ? gateways.split(" ") : [];
michael@0 6118 return options;
michael@0 6119 };
michael@0 6120
michael@0 6121 RilObject.prototype[REQUEST_DATA_CALL_LIST] = function REQUEST_DATA_CALL_LIST(length, options) {
michael@0 6122 if (options.rilRequestError) {
michael@0 6123 return;
michael@0 6124 }
michael@0 6125
michael@0 6126 if (!length) {
michael@0 6127 this._processDataCallList(null);
michael@0 6128 return;
michael@0 6129 }
michael@0 6130
michael@0 6131 let Buf = this.context.Buf;
michael@0 6132 let version = 0;
michael@0 6133 if (!this.v5Legacy) {
michael@0 6134 version = Buf.readInt32();
michael@0 6135 }
michael@0 6136 let num = Buf.readInt32();
michael@0 6137 let datacalls = {};
michael@0 6138 for (let i = 0; i < num; i++) {
michael@0 6139 let datacall;
michael@0 6140 if (version < 6) {
michael@0 6141 datacall = this.readDataCall_v5();
michael@0 6142 } else {
michael@0 6143 datacall = this.readDataCall_v6();
michael@0 6144 }
michael@0 6145 datacalls[datacall.cid] = datacall;
michael@0 6146 }
michael@0 6147
michael@0 6148 let newDataCallOptions = null;
michael@0 6149 if (options.rilRequestType == REQUEST_SETUP_DATA_CALL) {
michael@0 6150 newDataCallOptions = options;
michael@0 6151 }
michael@0 6152 this._processDataCallList(datacalls, newDataCallOptions);
michael@0 6153 };
michael@0 6154 RilObject.prototype[REQUEST_RESET_RADIO] = null;
michael@0 6155 RilObject.prototype[REQUEST_OEM_HOOK_RAW] = null;
michael@0 6156 RilObject.prototype[REQUEST_OEM_HOOK_STRINGS] = null;
michael@0 6157 RilObject.prototype[REQUEST_SCREEN_STATE] = null;
michael@0 6158 RilObject.prototype[REQUEST_SET_SUPP_SVC_NOTIFICATION] = null;
michael@0 6159 RilObject.prototype[REQUEST_WRITE_SMS_TO_SIM] = function REQUEST_WRITE_SMS_TO_SIM(length, options) {
michael@0 6160 if (options.rilRequestError) {
michael@0 6161 // `The MS shall return a "protocol error, unspecified" error message if
michael@0 6162 // the short message cannot be stored in the (U)SIM, and there is other
michael@0 6163 // message storage available at the MS` ~ 3GPP TS 23.038 section 4. Here
michael@0 6164 // we assume we always have indexed db as another storage.
michael@0 6165 this.acknowledgeGsmSms(false, PDU_FCS_PROTOCOL_ERROR);
michael@0 6166 } else {
michael@0 6167 this.acknowledgeGsmSms(true, PDU_FCS_OK);
michael@0 6168 }
michael@0 6169 };
michael@0 6170 RilObject.prototype[REQUEST_DELETE_SMS_ON_SIM] = null;
michael@0 6171 RilObject.prototype[REQUEST_SET_BAND_MODE] = null;
michael@0 6172 RilObject.prototype[REQUEST_QUERY_AVAILABLE_BAND_MODE] = null;
michael@0 6173 RilObject.prototype[REQUEST_STK_GET_PROFILE] = null;
michael@0 6174 RilObject.prototype[REQUEST_STK_SET_PROFILE] = null;
michael@0 6175 RilObject.prototype[REQUEST_STK_SEND_ENVELOPE_COMMAND] = null;
michael@0 6176 RilObject.prototype[REQUEST_STK_SEND_TERMINAL_RESPONSE] = null;
michael@0 6177 RilObject.prototype[REQUEST_STK_HANDLE_CALL_SETUP_REQUESTED_FROM_SIM] = null;
michael@0 6178 RilObject.prototype[REQUEST_EXPLICIT_CALL_TRANSFER] = null;
michael@0 6179 RilObject.prototype[REQUEST_SET_PREFERRED_NETWORK_TYPE] = function REQUEST_SET_PREFERRED_NETWORK_TYPE(length, options) {
michael@0 6180 if (options.networkType == null) {
michael@0 6181 // The request was made by ril_worker itself automatically. Don't report.
michael@0 6182 return;
michael@0 6183 }
michael@0 6184
michael@0 6185 if (options.rilRequestError) {
michael@0 6186 options.success = false;
michael@0 6187 options.errorMsg = RIL_ERROR_TO_GECKO_ERROR[options.rilRequestError];
michael@0 6188 } else {
michael@0 6189 options.success = true;
michael@0 6190 }
michael@0 6191 this.sendChromeMessage(options);
michael@0 6192 };
michael@0 6193 RilObject.prototype[REQUEST_GET_PREFERRED_NETWORK_TYPE] = function REQUEST_GET_PREFERRED_NETWORK_TYPE(length, options) {
michael@0 6194 if (options.rilRequestError) {
michael@0 6195 options.success = false;
michael@0 6196 options.errorMsg = RIL_ERROR_TO_GECKO_ERROR[options.rilRequestError];
michael@0 6197 this.sendChromeMessage(options);
michael@0 6198 return;
michael@0 6199 }
michael@0 6200
michael@0 6201 let results = this.context.Buf.readInt32List();
michael@0 6202 options.networkType = this.preferredNetworkType = results[0];
michael@0 6203 options.success = true;
michael@0 6204
michael@0 6205 this.sendChromeMessage(options);
michael@0 6206 };
michael@0 6207 RilObject.prototype[REQUEST_GET_NEIGHBORING_CELL_IDS] = null;
michael@0 6208 RilObject.prototype[REQUEST_SET_LOCATION_UPDATES] = null;
michael@0 6209 RilObject.prototype[REQUEST_CDMA_SET_SUBSCRIPTION_SOURCE] = null;
michael@0 6210 RilObject.prototype[REQUEST_CDMA_SET_ROAMING_PREFERENCE] = function REQUEST_CDMA_SET_ROAMING_PREFERENCE(length, options) {
michael@0 6211 if (options.rilRequestError) {
michael@0 6212 options.errorMsg = RIL_ERROR_TO_GECKO_ERROR[options.rilRequestError];
michael@0 6213 }
michael@0 6214 this.sendChromeMessage(options);
michael@0 6215 };
michael@0 6216 RilObject.prototype[REQUEST_CDMA_QUERY_ROAMING_PREFERENCE] = function REQUEST_CDMA_QUERY_ROAMING_PREFERENCE(length, options) {
michael@0 6217 if (options.rilRequestError) {
michael@0 6218 options.errorMsg = RIL_ERROR_TO_GECKO_ERROR[options.rilRequestError];
michael@0 6219 } else {
michael@0 6220 let mode = this.context.Buf.readInt32List();
michael@0 6221 options.mode = CDMA_ROAMING_PREFERENCE_TO_GECKO[mode[0]];
michael@0 6222 }
michael@0 6223 this.sendChromeMessage(options);
michael@0 6224 };
michael@0 6225 RilObject.prototype[REQUEST_SET_TTY_MODE] = null;
michael@0 6226 RilObject.prototype[REQUEST_QUERY_TTY_MODE] = null;
michael@0 6227 RilObject.prototype[REQUEST_CDMA_SET_PREFERRED_VOICE_PRIVACY_MODE] = function REQUEST_CDMA_SET_PREFERRED_VOICE_PRIVACY_MODE(length, options) {
michael@0 6228 if (options.rilRequestError) {
michael@0 6229 options.errorMsg = RIL_ERROR_TO_GECKO_ERROR[options.rilRequestError];
michael@0 6230 this.sendChromeMessage(options);
michael@0 6231 return;
michael@0 6232 }
michael@0 6233
michael@0 6234 this.sendChromeMessage(options);
michael@0 6235 };
michael@0 6236 RilObject.prototype[REQUEST_CDMA_QUERY_PREFERRED_VOICE_PRIVACY_MODE] = function REQUEST_CDMA_QUERY_PREFERRED_VOICE_PRIVACY_MODE(length, options) {
michael@0 6237 if (options.rilRequestError) {
michael@0 6238 options.errorMsg = RIL_ERROR_TO_GECKO_ERROR[options.rilRequestError];
michael@0 6239 this.sendChromeMessage(options);
michael@0 6240 return;
michael@0 6241 }
michael@0 6242
michael@0 6243 let enabled = this.context.Buf.readInt32List();
michael@0 6244 options.enabled = enabled[0] ? true : false;
michael@0 6245 this.sendChromeMessage(options);
michael@0 6246 };
michael@0 6247 RilObject.prototype[REQUEST_CDMA_FLASH] = function REQUEST_CDMA_FLASH(length, options) {
michael@0 6248 options.success = (options.rilRequestError === 0);
michael@0 6249 if (!options.success) {
michael@0 6250 if (options.rilMessageType === "conferenceCall") {
michael@0 6251 options.errorName = "addError";
michael@0 6252 } else if (options.rilMessageType === "separateCall") {
michael@0 6253 options.errorName = "removeError";
michael@0 6254 }
michael@0 6255 options.errorMsg = RIL_ERROR_TO_GECKO_ERROR[options.rilRequestError];
michael@0 6256 }
michael@0 6257
michael@0 6258 this.sendChromeMessage(options);
michael@0 6259 };
michael@0 6260 RilObject.prototype[REQUEST_CDMA_BURST_DTMF] = null;
michael@0 6261 RilObject.prototype[REQUEST_CDMA_VALIDATE_AND_WRITE_AKEY] = null;
michael@0 6262 RilObject.prototype[REQUEST_CDMA_SEND_SMS] = function REQUEST_CDMA_SEND_SMS(length, options) {
michael@0 6263 this._processSmsSendResult(length, options);
michael@0 6264 };
michael@0 6265 RilObject.prototype[REQUEST_CDMA_SMS_ACKNOWLEDGE] = null;
michael@0 6266 RilObject.prototype[REQUEST_GSM_GET_BROADCAST_SMS_CONFIG] = null;
michael@0 6267 RilObject.prototype[REQUEST_GSM_SET_BROADCAST_SMS_CONFIG] = function REQUEST_GSM_SET_BROADCAST_SMS_CONFIG(length, options) {
michael@0 6268 if (options.rilRequestError == ERROR_SUCCESS) {
michael@0 6269 this.setSmsBroadcastActivation(true);
michael@0 6270 }
michael@0 6271 };
michael@0 6272 RilObject.prototype[REQUEST_GSM_SMS_BROADCAST_ACTIVATION] = null;
michael@0 6273 RilObject.prototype[REQUEST_CDMA_GET_BROADCAST_SMS_CONFIG] = null;
michael@0 6274 RilObject.prototype[REQUEST_CDMA_SET_BROADCAST_SMS_CONFIG] = null;
michael@0 6275 RilObject.prototype[REQUEST_CDMA_SMS_BROADCAST_ACTIVATION] = null;
michael@0 6276 RilObject.prototype[REQUEST_CDMA_SUBSCRIPTION] = function REQUEST_CDMA_SUBSCRIPTION(length, options) {
michael@0 6277 if (options.rilRequestError) {
michael@0 6278 return;
michael@0 6279 }
michael@0 6280
michael@0 6281 let result = this.context.Buf.readStringList();
michael@0 6282
michael@0 6283 this.iccInfo.mdn = result[0];
michael@0 6284 // The result[1] is Home SID. (Already be handled in readCDMAHome())
michael@0 6285 // The result[2] is Home NID. (Already be handled in readCDMAHome())
michael@0 6286 // The result[3] is MIN.
michael@0 6287 this.iccInfo.prlVersion = parseInt(result[4], 10);
michael@0 6288
michael@0 6289 this.context.ICCUtilsHelper.handleICCInfoChange();
michael@0 6290 };
michael@0 6291 RilObject.prototype[REQUEST_CDMA_WRITE_SMS_TO_RUIM] = null;
michael@0 6292 RilObject.prototype[REQUEST_CDMA_DELETE_SMS_ON_RUIM] = null;
michael@0 6293 RilObject.prototype[REQUEST_DEVICE_IDENTITY] = function REQUEST_DEVICE_IDENTITY(length, options) {
michael@0 6294 if (options.rilRequestError) {
michael@0 6295 return;
michael@0 6296 }
michael@0 6297
michael@0 6298 let result = this.context.Buf.readStringList();
michael@0 6299
michael@0 6300 // The result[0] is for IMEI. (Already be handled in REQUEST_GET_IMEI)
michael@0 6301 // The result[1] is for IMEISV. (Already be handled in REQUEST_GET_IMEISV)
michael@0 6302 // They are both ignored.
michael@0 6303 this.ESN = result[2];
michael@0 6304 this.MEID = result[3];
michael@0 6305 };
michael@0 6306 RilObject.prototype[REQUEST_EXIT_EMERGENCY_CALLBACK_MODE] = function REQUEST_EXIT_EMERGENCY_CALLBACK_MODE(length, options) {
michael@0 6307 if (options.internal) {
michael@0 6308 return;
michael@0 6309 }
michael@0 6310
michael@0 6311 options.success = (options.rilRequestError === 0);
michael@0 6312 if (!options.success) {
michael@0 6313 options.errorMsg = RIL_ERROR_TO_GECKO_ERROR[options.rilRequestError];
michael@0 6314 }
michael@0 6315 this.sendChromeMessage(options);
michael@0 6316 };
michael@0 6317 RilObject.prototype[REQUEST_GET_SMSC_ADDRESS] = function REQUEST_GET_SMSC_ADDRESS(length, options) {
michael@0 6318 this.SMSC = options.rilRequestError ? null : this.context.Buf.readString();
michael@0 6319
michael@0 6320 if (!options.rilMessageType || options.rilMessageType !== "getSmscAddress") {
michael@0 6321 return;
michael@0 6322 }
michael@0 6323
michael@0 6324 options.smscAddress = this.SMSC;
michael@0 6325 options.errorMsg = RIL_ERROR_TO_GECKO_ERROR[options.rilRequestError];
michael@0 6326 this.sendChromeMessage(options);
michael@0 6327 };
michael@0 6328 RilObject.prototype[REQUEST_SET_SMSC_ADDRESS] = null;
michael@0 6329 RilObject.prototype[REQUEST_REPORT_SMS_MEMORY_STATUS] = null;
michael@0 6330 RilObject.prototype[REQUEST_REPORT_STK_SERVICE_IS_RUNNING] = null;
michael@0 6331 RilObject.prototype[REQUEST_ACKNOWLEDGE_INCOMING_GSM_SMS_WITH_PDU] = null;
michael@0 6332 RilObject.prototype[REQUEST_STK_SEND_ENVELOPE_WITH_STATUS] = function REQUEST_STK_SEND_ENVELOPE_WITH_STATUS(length, options) {
michael@0 6333 if (options.rilRequestError) {
michael@0 6334 this.acknowledgeGsmSms(false, PDU_FCS_UNSPECIFIED);
michael@0 6335 return;
michael@0 6336 }
michael@0 6337
michael@0 6338 let Buf = this.context.Buf;
michael@0 6339 let sw1 = Buf.readInt32();
michael@0 6340 let sw2 = Buf.readInt32();
michael@0 6341 if ((sw1 == ICC_STATUS_SAT_BUSY) && (sw2 === 0x00)) {
michael@0 6342 this.acknowledgeGsmSms(false, PDU_FCS_USAT_BUSY);
michael@0 6343 return;
michael@0 6344 }
michael@0 6345
michael@0 6346 let success = ((sw1 == ICC_STATUS_NORMAL_ENDING) && (sw2 === 0x00))
michael@0 6347 || (sw1 == ICC_STATUS_NORMAL_ENDING_WITH_EXTRA);
michael@0 6348
michael@0 6349 let messageStringLength = Buf.readInt32(); // In semi-octets
michael@0 6350 let responsePduLen = messageStringLength / 2; // In octets
michael@0 6351 if (!responsePduLen) {
michael@0 6352 this.acknowledgeGsmSms(success, success ? PDU_FCS_OK
michael@0 6353 : PDU_FCS_USIM_DATA_DOWNLOAD_ERROR);
michael@0 6354 return;
michael@0 6355 }
michael@0 6356
michael@0 6357 this.acknowledgeIncomingGsmSmsWithPDU(success, responsePduLen, options);
michael@0 6358 };
michael@0 6359 RilObject.prototype[REQUEST_VOICE_RADIO_TECH] = function REQUEST_VOICE_RADIO_TECH(length, options) {
michael@0 6360 if (options.rilRequestError) {
michael@0 6361 if (DEBUG) {
michael@0 6362 this.context.debug("Error when getting voice radio tech: " +
michael@0 6363 options.rilRequestError);
michael@0 6364 }
michael@0 6365 return;
michael@0 6366 }
michael@0 6367 let radioTech = this.context.Buf.readInt32List();
michael@0 6368 this._processRadioTech(radioTech[0]);
michael@0 6369 };
michael@0 6370 RilObject.prototype[REQUEST_GET_UNLOCK_RETRY_COUNT] = function REQUEST_GET_UNLOCK_RETRY_COUNT(length, options) {
michael@0 6371 options.success = (options.rilRequestError === 0);
michael@0 6372 if (!options.success) {
michael@0 6373 options.errorMsg = RIL_ERROR_TO_GECKO_ERROR[options.rilRequestError];
michael@0 6374 }
michael@0 6375 options.retryCount = length ? this.context.Buf.readInt32List()[0] : -1;
michael@0 6376 this.sendChromeMessage(options);
michael@0 6377 };
michael@0 6378 RilObject.prototype[RIL_REQUEST_GPRS_ATTACH] = null;
michael@0 6379 RilObject.prototype[RIL_REQUEST_GPRS_DETACH] = null;
michael@0 6380 RilObject.prototype[UNSOLICITED_RESPONSE_RADIO_STATE_CHANGED] = function UNSOLICITED_RESPONSE_RADIO_STATE_CHANGED() {
michael@0 6381 let radioState = this.context.Buf.readInt32();
michael@0 6382 let newState;
michael@0 6383 if (radioState == RADIO_STATE_UNAVAILABLE) {
michael@0 6384 newState = GECKO_RADIOSTATE_UNAVAILABLE;
michael@0 6385 } else if (radioState == RADIO_STATE_OFF) {
michael@0 6386 newState = GECKO_RADIOSTATE_OFF;
michael@0 6387 } else {
michael@0 6388 newState = GECKO_RADIOSTATE_READY;
michael@0 6389 }
michael@0 6390
michael@0 6391 if (DEBUG) {
michael@0 6392 this.context.debug("Radio state changed from '" + this.radioState +
michael@0 6393 "' to '" + newState + "'");
michael@0 6394 }
michael@0 6395 if (this.radioState == newState) {
michael@0 6396 return;
michael@0 6397 }
michael@0 6398
michael@0 6399 switch (radioState) {
michael@0 6400 case RADIO_STATE_SIM_READY:
michael@0 6401 case RADIO_STATE_SIM_NOT_READY:
michael@0 6402 case RADIO_STATE_SIM_LOCKED_OR_ABSENT:
michael@0 6403 this._isCdma = false;
michael@0 6404 this._waitingRadioTech = false;
michael@0 6405 break;
michael@0 6406 case RADIO_STATE_RUIM_READY:
michael@0 6407 case RADIO_STATE_RUIM_NOT_READY:
michael@0 6408 case RADIO_STATE_RUIM_LOCKED_OR_ABSENT:
michael@0 6409 case RADIO_STATE_NV_READY:
michael@0 6410 case RADIO_STATE_NV_NOT_READY:
michael@0 6411 this._isCdma = true;
michael@0 6412 this._waitingRadioTech = false;
michael@0 6413 break;
michael@0 6414 case RADIO_STATE_ON: // RIL v7
michael@0 6415 // This value is defined in RIL v7, we will retrieve radio tech by another
michael@0 6416 // request. We leave _isCdma untouched, and it will be set once we get the
michael@0 6417 // radio technology.
michael@0 6418 this._waitingRadioTech = true;
michael@0 6419 this.getVoiceRadioTechnology();
michael@0 6420 break;
michael@0 6421 }
michael@0 6422
michael@0 6423 if ((this.radioState == GECKO_RADIOSTATE_UNAVAILABLE ||
michael@0 6424 this.radioState == GECKO_RADIOSTATE_OFF) &&
michael@0 6425 newState == GECKO_RADIOSTATE_READY) {
michael@0 6426 // The radio became available, let's get its info.
michael@0 6427 if (!this._waitingRadioTech) {
michael@0 6428 if (this._isCdma) {
michael@0 6429 this.getDeviceIdentity();
michael@0 6430 } else {
michael@0 6431 this.getIMEI();
michael@0 6432 this.getIMEISV();
michael@0 6433 }
michael@0 6434 }
michael@0 6435 this.getBasebandVersion();
michael@0 6436 this.updateCellBroadcastConfig();
michael@0 6437 this.setPreferredNetworkType();
michael@0 6438 this.setCLIR();
michael@0 6439 if (RILQUIRKS_DATA_REGISTRATION_ON_DEMAND && this._attachDataRegistration) {
michael@0 6440 this.setDataRegistration({attach: true});
michael@0 6441 }
michael@0 6442 }
michael@0 6443
michael@0 6444 this.radioState = newState;
michael@0 6445 this.sendChromeMessage({
michael@0 6446 rilMessageType: "radiostatechange",
michael@0 6447 radioState: newState
michael@0 6448 });
michael@0 6449
michael@0 6450 // If the radio is up and on, so let's query the card state.
michael@0 6451 // On older RILs only if the card is actually ready, though.
michael@0 6452 // If _waitingRadioTech is set, we don't need to get icc status now.
michael@0 6453 if (radioState == RADIO_STATE_UNAVAILABLE ||
michael@0 6454 radioState == RADIO_STATE_OFF ||
michael@0 6455 this._waitingRadioTech) {
michael@0 6456 return;
michael@0 6457 }
michael@0 6458 this.getICCStatus();
michael@0 6459 };
michael@0 6460 RilObject.prototype[UNSOLICITED_RESPONSE_CALL_STATE_CHANGED] = function UNSOLICITED_RESPONSE_CALL_STATE_CHANGED() {
michael@0 6461 this.getCurrentCalls();
michael@0 6462 };
michael@0 6463 RilObject.prototype[UNSOLICITED_RESPONSE_VOICE_NETWORK_STATE_CHANGED] = function UNSOLICITED_RESPONSE_VOICE_NETWORK_STATE_CHANGED() {
michael@0 6464 if (DEBUG) {
michael@0 6465 this.context.debug("Network state changed, re-requesting phone state and " +
michael@0 6466 "ICC status");
michael@0 6467 }
michael@0 6468 this.getICCStatus();
michael@0 6469 this.requestNetworkInfo();
michael@0 6470 };
michael@0 6471 RilObject.prototype[UNSOLICITED_RESPONSE_NEW_SMS] = function UNSOLICITED_RESPONSE_NEW_SMS(length) {
michael@0 6472 let [message, result] = this.context.GsmPDUHelper.processReceivedSms(length);
michael@0 6473
michael@0 6474 if (message) {
michael@0 6475 result = this._processSmsMultipart(message);
michael@0 6476 }
michael@0 6477
michael@0 6478 if (result == PDU_FCS_RESERVED || result == MOZ_FCS_WAIT_FOR_EXPLICIT_ACK) {
michael@0 6479 return;
michael@0 6480 }
michael@0 6481
michael@0 6482 // Not reserved FCS values, send ACK now.
michael@0 6483 this.acknowledgeGsmSms(result == PDU_FCS_OK, result);
michael@0 6484 };
michael@0 6485 RilObject.prototype[UNSOLICITED_RESPONSE_NEW_SMS_STATUS_REPORT] = function UNSOLICITED_RESPONSE_NEW_SMS_STATUS_REPORT(length) {
michael@0 6486 let result = this._processSmsStatusReport(length);
michael@0 6487 this.acknowledgeGsmSms(result == PDU_FCS_OK, result);
michael@0 6488 };
michael@0 6489 RilObject.prototype[UNSOLICITED_RESPONSE_NEW_SMS_ON_SIM] = function UNSOLICITED_RESPONSE_NEW_SMS_ON_SIM(length) {
michael@0 6490 let recordNumber = this.context.Buf.readInt32List()[0];
michael@0 6491
michael@0 6492 this.context.SimRecordHelper.readSMS(
michael@0 6493 recordNumber,
michael@0 6494 function onsuccess(message) {
michael@0 6495 if (message && message.simStatus === 3) { //New Unread SMS
michael@0 6496 this._processSmsMultipart(message);
michael@0 6497 }
michael@0 6498 }.bind(this),
michael@0 6499 function onerror(errorMsg) {
michael@0 6500 if (DEBUG) {
michael@0 6501 this.context.debug("Failed to Read NEW SMS on SIM #" + recordNumber +
michael@0 6502 ", errorMsg: " + errorMsg);
michael@0 6503 }
michael@0 6504 });
michael@0 6505 };
michael@0 6506 RilObject.prototype[UNSOLICITED_ON_USSD] = function UNSOLICITED_ON_USSD() {
michael@0 6507 let [typeCode, message] = this.context.Buf.readStringList();
michael@0 6508 if (DEBUG) {
michael@0 6509 this.context.debug("On USSD. Type Code: " + typeCode + " Message: " + message);
michael@0 6510 }
michael@0 6511
michael@0 6512 this._ussdSession = (typeCode != "0" && typeCode != "2");
michael@0 6513
michael@0 6514 this.sendChromeMessage({rilMessageType: "USSDReceived",
michael@0 6515 message: message,
michael@0 6516 sessionEnded: !this._ussdSession});
michael@0 6517 };
michael@0 6518 RilObject.prototype[UNSOLICITED_NITZ_TIME_RECEIVED] = function UNSOLICITED_NITZ_TIME_RECEIVED() {
michael@0 6519 let dateString = this.context.Buf.readString();
michael@0 6520
michael@0 6521 // The data contained in the NITZ message is
michael@0 6522 // in the form "yy/mm/dd,hh:mm:ss(+/-)tz,dt"
michael@0 6523 // for example: 12/02/16,03:36:08-20,00,310410
michael@0 6524 // See also bug 714352 - Listen for NITZ updates from rild.
michael@0 6525
michael@0 6526 if (DEBUG) this.context.debug("DateTimeZone string " + dateString);
michael@0 6527
michael@0 6528 let now = Date.now();
michael@0 6529
michael@0 6530 let year = parseInt(dateString.substr(0, 2), 10);
michael@0 6531 let month = parseInt(dateString.substr(3, 2), 10);
michael@0 6532 let day = parseInt(dateString.substr(6, 2), 10);
michael@0 6533 let hours = parseInt(dateString.substr(9, 2), 10);
michael@0 6534 let minutes = parseInt(dateString.substr(12, 2), 10);
michael@0 6535 let seconds = parseInt(dateString.substr(15, 2), 10);
michael@0 6536 // Note that |tz| is in 15-min units.
michael@0 6537 let tz = parseInt(dateString.substr(17, 3), 10);
michael@0 6538 // Note that |dst| is in 1-hour units and is already applied in |tz|.
michael@0 6539 let dst = parseInt(dateString.substr(21, 2), 10);
michael@0 6540
michael@0 6541 let timeInMS = Date.UTC(year + PDU_TIMESTAMP_YEAR_OFFSET, month - 1, day,
michael@0 6542 hours, minutes, seconds);
michael@0 6543
michael@0 6544 if (isNaN(timeInMS)) {
michael@0 6545 if (DEBUG) this.context.debug("NITZ failed to convert date");
michael@0 6546 return;
michael@0 6547 }
michael@0 6548
michael@0 6549 this.sendChromeMessage({rilMessageType: "nitzTime",
michael@0 6550 networkTimeInMS: timeInMS,
michael@0 6551 networkTimeZoneInMinutes: -(tz * 15),
michael@0 6552 networkDSTInMinutes: -(dst * 60),
michael@0 6553 receiveTimeInMS: now});
michael@0 6554 };
michael@0 6555
michael@0 6556 RilObject.prototype[UNSOLICITED_SIGNAL_STRENGTH] = function UNSOLICITED_SIGNAL_STRENGTH(length) {
michael@0 6557 this[REQUEST_SIGNAL_STRENGTH](length, {rilRequestError: ERROR_SUCCESS});
michael@0 6558 };
michael@0 6559 RilObject.prototype[UNSOLICITED_DATA_CALL_LIST_CHANGED] = function UNSOLICITED_DATA_CALL_LIST_CHANGED(length) {
michael@0 6560 if (this.v5Legacy) {
michael@0 6561 this.getDataCallList();
michael@0 6562 return;
michael@0 6563 }
michael@0 6564 this[REQUEST_DATA_CALL_LIST](length, {rilRequestError: ERROR_SUCCESS});
michael@0 6565 };
michael@0 6566 RilObject.prototype[UNSOLICITED_SUPP_SVC_NOTIFICATION] = function UNSOLICITED_SUPP_SVC_NOTIFICATION(length) {
michael@0 6567 let Buf = this.context.Buf;
michael@0 6568 let info = {};
michael@0 6569 info.notificationType = Buf.readInt32();
michael@0 6570 info.code = Buf.readInt32();
michael@0 6571 info.index = Buf.readInt32();
michael@0 6572 info.type = Buf.readInt32();
michael@0 6573 info.number = Buf.readString();
michael@0 6574
michael@0 6575 this._processSuppSvcNotification(info);
michael@0 6576 };
michael@0 6577
michael@0 6578 RilObject.prototype[UNSOLICITED_STK_SESSION_END] = function UNSOLICITED_STK_SESSION_END() {
michael@0 6579 this.sendChromeMessage({rilMessageType: "stksessionend"});
michael@0 6580 };
michael@0 6581 RilObject.prototype[UNSOLICITED_STK_PROACTIVE_COMMAND] = function UNSOLICITED_STK_PROACTIVE_COMMAND() {
michael@0 6582 this.processStkProactiveCommand();
michael@0 6583 };
michael@0 6584 RilObject.prototype[UNSOLICITED_STK_EVENT_NOTIFY] = function UNSOLICITED_STK_EVENT_NOTIFY() {
michael@0 6585 this.processStkProactiveCommand();
michael@0 6586 };
michael@0 6587 RilObject.prototype[UNSOLICITED_STK_CALL_SETUP] = null;
michael@0 6588 RilObject.prototype[UNSOLICITED_SIM_SMS_STORAGE_FULL] = null;
michael@0 6589 RilObject.prototype[UNSOLICITED_SIM_REFRESH] = null;
michael@0 6590 RilObject.prototype[UNSOLICITED_CALL_RING] = function UNSOLICITED_CALL_RING() {
michael@0 6591 let Buf = this.context.Buf;
michael@0 6592 let info = {rilMessageType: "callRing"};
michael@0 6593 let isCDMA = false; //XXX TODO hard-code this for now
michael@0 6594 if (isCDMA) {
michael@0 6595 info.isPresent = Buf.readInt32();
michael@0 6596 info.signalType = Buf.readInt32();
michael@0 6597 info.alertPitch = Buf.readInt32();
michael@0 6598 info.signal = Buf.readInt32();
michael@0 6599 }
michael@0 6600 // At this point we don't know much other than the fact there's an incoming
michael@0 6601 // call, but that's enough to bring up the Phone app already. We'll know
michael@0 6602 // details once we get a call state changed notification and can then
michael@0 6603 // dispatch DOM events etc.
michael@0 6604 this.sendChromeMessage(info);
michael@0 6605 };
michael@0 6606 RilObject.prototype[UNSOLICITED_RESPONSE_SIM_STATUS_CHANGED] = function UNSOLICITED_RESPONSE_SIM_STATUS_CHANGED() {
michael@0 6607 this.getICCStatus();
michael@0 6608 };
michael@0 6609 RilObject.prototype[UNSOLICITED_RESPONSE_CDMA_NEW_SMS] = function UNSOLICITED_RESPONSE_CDMA_NEW_SMS(length) {
michael@0 6610 let [message, result] = this.context.CdmaPDUHelper.processReceivedSms(length);
michael@0 6611
michael@0 6612 if (message) {
michael@0 6613 if (message.teleservice === PDU_CDMA_MSG_TELESERIVCIE_ID_WAP) {
michael@0 6614 result = this._processCdmaSmsWapPush(message);
michael@0 6615 } else if (message.subMsgType === PDU_CDMA_MSG_TYPE_DELIVER_ACK) {
michael@0 6616 result = this._processCdmaSmsStatusReport(message);
michael@0 6617 } else {
michael@0 6618 result = this._processSmsMultipart(message);
michael@0 6619 }
michael@0 6620 }
michael@0 6621
michael@0 6622 if (result == PDU_FCS_RESERVED || result == MOZ_FCS_WAIT_FOR_EXPLICIT_ACK) {
michael@0 6623 return;
michael@0 6624 }
michael@0 6625
michael@0 6626 // Not reserved FCS values, send ACK now.
michael@0 6627 this.acknowledgeCdmaSms(result == PDU_FCS_OK, result);
michael@0 6628 };
michael@0 6629 RilObject.prototype[UNSOLICITED_RESPONSE_NEW_BROADCAST_SMS] = function UNSOLICITED_RESPONSE_NEW_BROADCAST_SMS(length) {
michael@0 6630 let message;
michael@0 6631 try {
michael@0 6632 message =
michael@0 6633 this.context.GsmPDUHelper.readCbMessage(this.context.Buf.readInt32());
michael@0 6634 } catch (e) {
michael@0 6635 if (DEBUG) {
michael@0 6636 this.context.debug("Failed to parse Cell Broadcast message: " +
michael@0 6637 JSON.stringify(e));
michael@0 6638 }
michael@0 6639 return;
michael@0 6640 }
michael@0 6641
michael@0 6642 message = this._processReceivedSmsCbPage(message);
michael@0 6643 if (!message) {
michael@0 6644 return;
michael@0 6645 }
michael@0 6646
michael@0 6647 message.rilMessageType = "cellbroadcast-received";
michael@0 6648 this.sendChromeMessage(message);
michael@0 6649 };
michael@0 6650 RilObject.prototype[UNSOLICITED_CDMA_RUIM_SMS_STORAGE_FULL] = null;
michael@0 6651 RilObject.prototype[UNSOLICITED_RESTRICTED_STATE_CHANGED] = null;
michael@0 6652 RilObject.prototype[UNSOLICITED_ENTER_EMERGENCY_CALLBACK_MODE] = function UNSOLICITED_ENTER_EMERGENCY_CALLBACK_MODE() {
michael@0 6653 this._handleChangedEmergencyCbMode(true);
michael@0 6654 };
michael@0 6655 RilObject.prototype[UNSOLICITED_CDMA_CALL_WAITING] = function UNSOLICITED_CDMA_CALL_WAITING(length) {
michael@0 6656 let Buf = this.context.Buf;
michael@0 6657 let call = {};
michael@0 6658 call.number = Buf.readString();
michael@0 6659 call.numberPresentation = Buf.readInt32();
michael@0 6660 call.name = Buf.readString();
michael@0 6661 call.namePresentation = Buf.readInt32();
michael@0 6662 call.isPresent = Buf.readInt32();
michael@0 6663 call.signalType = Buf.readInt32();
michael@0 6664 call.alertPitch = Buf.readInt32();
michael@0 6665 call.signal = Buf.readInt32();
michael@0 6666 this.sendChromeMessage({rilMessageType: "cdmaCallWaiting",
michael@0 6667 number: call.number});
michael@0 6668 };
michael@0 6669 RilObject.prototype[UNSOLICITED_CDMA_OTA_PROVISION_STATUS] = function UNSOLICITED_CDMA_OTA_PROVISION_STATUS() {
michael@0 6670 let status = this.context.Buf.readInt32List()[0];
michael@0 6671 this.sendChromeMessage({rilMessageType: "otastatuschange",
michael@0 6672 status: status});
michael@0 6673 };
michael@0 6674 RilObject.prototype[UNSOLICITED_CDMA_INFO_REC] = function UNSOLICITED_CDMA_INFO_REC(length) {
michael@0 6675 let record = this.context.CdmaPDUHelper.decodeInformationRecord();
michael@0 6676 record.rilMessageType = "cdma-info-rec-received";
michael@0 6677 this.sendChromeMessage(record);
michael@0 6678 };
michael@0 6679 RilObject.prototype[UNSOLICITED_OEM_HOOK_RAW] = null;
michael@0 6680 RilObject.prototype[UNSOLICITED_RINGBACK_TONE] = null;
michael@0 6681 RilObject.prototype[UNSOLICITED_RESEND_INCALL_MUTE] = null;
michael@0 6682 RilObject.prototype[UNSOLICITED_CDMA_SUBSCRIPTION_SOURCE_CHANGED] = null;
michael@0 6683 RilObject.prototype[UNSOLICITED_CDMA_PRL_CHANGED] = function UNSOLICITED_CDMA_PRL_CHANGED(length) {
michael@0 6684 let version = this.context.Buf.readInt32List()[0];
michael@0 6685 if (version !== this.iccInfo.prlVersion) {
michael@0 6686 this.iccInfo.prlVersion = version;
michael@0 6687 this.context.ICCUtilsHelper.handleICCInfoChange();
michael@0 6688 }
michael@0 6689 };
michael@0 6690 RilObject.prototype[UNSOLICITED_EXIT_EMERGENCY_CALLBACK_MODE] = function UNSOLICITED_EXIT_EMERGENCY_CALLBACK_MODE() {
michael@0 6691 this._handleChangedEmergencyCbMode(false);
michael@0 6692 };
michael@0 6693 RilObject.prototype[UNSOLICITED_RIL_CONNECTED] = function UNSOLICITED_RIL_CONNECTED(length) {
michael@0 6694 // Prevent response id collision between UNSOLICITED_RIL_CONNECTED and
michael@0 6695 // UNSOLICITED_VOICE_RADIO_TECH_CHANGED for Akami on gingerbread branch.
michael@0 6696 if (!length) {
michael@0 6697 return;
michael@0 6698 }
michael@0 6699
michael@0 6700 let version = this.context.Buf.readInt32List()[0];
michael@0 6701 this.v5Legacy = (version < 5);
michael@0 6702 if (DEBUG) {
michael@0 6703 this.context.debug("Detected RIL version " + version);
michael@0 6704 this.context.debug("this.v5Legacy is " + this.v5Legacy);
michael@0 6705 }
michael@0 6706
michael@0 6707 this.initRILState();
michael@0 6708 // Always ensure that we are not in emergency callback mode when init.
michael@0 6709 this.exitEmergencyCbMode();
michael@0 6710 // Reset radio in the case that b2g restart (or crash).
michael@0 6711 this.setRadioEnabled({enabled: false});
michael@0 6712 };
michael@0 6713
michael@0 6714 /**
michael@0 6715 * This object exposes the functionality to parse and serialize PDU strings
michael@0 6716 *
michael@0 6717 * A PDU is a string containing a series of hexadecimally encoded octets
michael@0 6718 * or nibble-swapped binary-coded decimals (BCDs). It contains not only the
michael@0 6719 * message text but information about the sender, the SMS service center,
michael@0 6720 * timestamp, etc.
michael@0 6721 */
michael@0 6722 function GsmPDUHelperObject(aContext) {
michael@0 6723 this.context = aContext;
michael@0 6724 }
michael@0 6725 GsmPDUHelperObject.prototype = {
michael@0 6726 context: null,
michael@0 6727
michael@0 6728 /**
michael@0 6729 * Read one character (2 bytes) from a RIL string and decode as hex.
michael@0 6730 *
michael@0 6731 * @return the nibble as a number.
michael@0 6732 */
michael@0 6733 readHexNibble: function() {
michael@0 6734 let nibble = this.context.Buf.readUint16();
michael@0 6735 if (nibble >= 48 && nibble <= 57) {
michael@0 6736 nibble -= 48; // ASCII '0'..'9'
michael@0 6737 } else if (nibble >= 65 && nibble <= 70) {
michael@0 6738 nibble -= 55; // ASCII 'A'..'F'
michael@0 6739 } else if (nibble >= 97 && nibble <= 102) {
michael@0 6740 nibble -= 87; // ASCII 'a'..'f'
michael@0 6741 } else {
michael@0 6742 throw "Found invalid nibble during PDU parsing: " +
michael@0 6743 String.fromCharCode(nibble);
michael@0 6744 }
michael@0 6745 return nibble;
michael@0 6746 },
michael@0 6747
michael@0 6748 /**
michael@0 6749 * Encode a nibble as one hex character in a RIL string (2 bytes).
michael@0 6750 *
michael@0 6751 * @param nibble
michael@0 6752 * The nibble to encode (represented as a number)
michael@0 6753 */
michael@0 6754 writeHexNibble: function(nibble) {
michael@0 6755 nibble &= 0x0f;
michael@0 6756 if (nibble < 10) {
michael@0 6757 nibble += 48; // ASCII '0'
michael@0 6758 } else {
michael@0 6759 nibble += 55; // ASCII 'A'
michael@0 6760 }
michael@0 6761 this.context.Buf.writeUint16(nibble);
michael@0 6762 },
michael@0 6763
michael@0 6764 /**
michael@0 6765 * Read a hex-encoded octet (two nibbles).
michael@0 6766 *
michael@0 6767 * @return the octet as a number.
michael@0 6768 */
michael@0 6769 readHexOctet: function() {
michael@0 6770 return (this.readHexNibble() << 4) | this.readHexNibble();
michael@0 6771 },
michael@0 6772
michael@0 6773 /**
michael@0 6774 * Write an octet as two hex-encoded nibbles.
michael@0 6775 *
michael@0 6776 * @param octet
michael@0 6777 * The octet (represented as a number) to encode.
michael@0 6778 */
michael@0 6779 writeHexOctet: function(octet) {
michael@0 6780 this.writeHexNibble(octet >> 4);
michael@0 6781 this.writeHexNibble(octet);
michael@0 6782 },
michael@0 6783
michael@0 6784 /**
michael@0 6785 * Read an array of hex-encoded octets.
michael@0 6786 */
michael@0 6787 readHexOctetArray: function(length) {
michael@0 6788 let array = new Uint8Array(length);
michael@0 6789 for (let i = 0; i < length; i++) {
michael@0 6790 array[i] = this.readHexOctet();
michael@0 6791 }
michael@0 6792 return array;
michael@0 6793 },
michael@0 6794
michael@0 6795 /**
michael@0 6796 * Convert an octet (number) to a BCD number.
michael@0 6797 *
michael@0 6798 * Any nibbles that are not in the BCD range count as 0.
michael@0 6799 *
michael@0 6800 * @param octet
michael@0 6801 * The octet (a number, as returned by getOctet())
michael@0 6802 *
michael@0 6803 * @return the corresponding BCD number.
michael@0 6804 */
michael@0 6805 octetToBCD: function(octet) {
michael@0 6806 return ((octet & 0xf0) <= 0x90) * ((octet >> 4) & 0x0f) +
michael@0 6807 ((octet & 0x0f) <= 0x09) * (octet & 0x0f) * 10;
michael@0 6808 },
michael@0 6809
michael@0 6810 /**
michael@0 6811 * Convert a BCD number to an octet (number)
michael@0 6812 *
michael@0 6813 * Only take two digits with absolute value.
michael@0 6814 *
michael@0 6815 * @param bcd
michael@0 6816 *
michael@0 6817 * @return the corresponding octet.
michael@0 6818 */
michael@0 6819 BCDToOctet: function(bcd) {
michael@0 6820 bcd = Math.abs(bcd);
michael@0 6821 return ((bcd % 10) << 4) + (Math.floor(bcd / 10) % 10);
michael@0 6822 },
michael@0 6823
michael@0 6824 /**
michael@0 6825 * Convert a semi-octet (number) to a GSM BCD char, or return empty string
michael@0 6826 * if invalid semiOctet and supressException is set to true.
michael@0 6827 *
michael@0 6828 * @param semiOctet
michael@0 6829 * Nibble to be converted to.
michael@0 6830 * @param [optional] supressException
michael@0 6831 * Supress exception if invalid semiOctet and supressException is set
michael@0 6832 * to true.
michael@0 6833 *
michael@0 6834 * @return GSM BCD char, or empty string.
michael@0 6835 */
michael@0 6836 bcdChars: "0123456789*#,;",
michael@0 6837 semiOctetToBcdChar: function(semiOctet, supressException) {
michael@0 6838 if (semiOctet >= 14) {
michael@0 6839 if (supressException) {
michael@0 6840 return "";
michael@0 6841 } else {
michael@0 6842 throw new RangeError();
michael@0 6843 }
michael@0 6844 }
michael@0 6845
michael@0 6846 return this.bcdChars.charAt(semiOctet);
michael@0 6847 },
michael@0 6848
michael@0 6849 /**
michael@0 6850 * Read a *swapped nibble* binary coded decimal (BCD)
michael@0 6851 *
michael@0 6852 * @param pairs
michael@0 6853 * Number of nibble *pairs* to read.
michael@0 6854 *
michael@0 6855 * @return the decimal as a number.
michael@0 6856 */
michael@0 6857 readSwappedNibbleBcdNum: function(pairs) {
michael@0 6858 let number = 0;
michael@0 6859 for (let i = 0; i < pairs; i++) {
michael@0 6860 let octet = this.readHexOctet();
michael@0 6861 // Ignore 'ff' octets as they're often used as filler.
michael@0 6862 if (octet == 0xff) {
michael@0 6863 continue;
michael@0 6864 }
michael@0 6865 // If the first nibble is an "F" , only the second nibble is to be taken
michael@0 6866 // into account.
michael@0 6867 if ((octet & 0xf0) == 0xf0) {
michael@0 6868 number *= 10;
michael@0 6869 number += octet & 0x0f;
michael@0 6870 continue;
michael@0 6871 }
michael@0 6872 number *= 100;
michael@0 6873 number += this.octetToBCD(octet);
michael@0 6874 }
michael@0 6875 return number;
michael@0 6876 },
michael@0 6877
michael@0 6878 /**
michael@0 6879 * Read a *swapped nibble* binary coded string (BCD)
michael@0 6880 *
michael@0 6881 * @param pairs
michael@0 6882 * Number of nibble *pairs* to read.
michael@0 6883 * @param [optional] supressException
michael@0 6884 * Supress exception if invalid semiOctet and supressException is set
michael@0 6885 * to true.
michael@0 6886 *
michael@0 6887 * @return The BCD string.
michael@0 6888 */
michael@0 6889 readSwappedNibbleBcdString: function(pairs, supressException) {
michael@0 6890 let str = "";
michael@0 6891 for (let i = 0; i < pairs; i++) {
michael@0 6892 let nibbleH = this.readHexNibble();
michael@0 6893 let nibbleL = this.readHexNibble();
michael@0 6894 if (nibbleL == 0x0F) {
michael@0 6895 break;
michael@0 6896 }
michael@0 6897
michael@0 6898 str += this.semiOctetToBcdChar(nibbleL, supressException);
michael@0 6899 if (nibbleH != 0x0F) {
michael@0 6900 str += this.semiOctetToBcdChar(nibbleH, supressException);
michael@0 6901 }
michael@0 6902 }
michael@0 6903
michael@0 6904 return str;
michael@0 6905 },
michael@0 6906
michael@0 6907 /**
michael@0 6908 * Write numerical data as swapped nibble BCD.
michael@0 6909 *
michael@0 6910 * @param data
michael@0 6911 * Data to write (as a string or a number)
michael@0 6912 */
michael@0 6913 writeSwappedNibbleBCD: function(data) {
michael@0 6914 data = data.toString();
michael@0 6915 if (data.length % 2) {
michael@0 6916 data += "F";
michael@0 6917 }
michael@0 6918 let Buf = this.context.Buf;
michael@0 6919 for (let i = 0; i < data.length; i += 2) {
michael@0 6920 Buf.writeUint16(data.charCodeAt(i + 1));
michael@0 6921 Buf.writeUint16(data.charCodeAt(i));
michael@0 6922 }
michael@0 6923 },
michael@0 6924
michael@0 6925 /**
michael@0 6926 * Write numerical data as swapped nibble BCD.
michael@0 6927 * If the number of digit of data is even, add '0' at the beginning.
michael@0 6928 *
michael@0 6929 * @param data
michael@0 6930 * Data to write (as a string or a number)
michael@0 6931 */
michael@0 6932 writeSwappedNibbleBCDNum: function(data) {
michael@0 6933 data = data.toString();
michael@0 6934 if (data.length % 2) {
michael@0 6935 data = "0" + data;
michael@0 6936 }
michael@0 6937 let Buf = this.context.Buf;
michael@0 6938 for (let i = 0; i < data.length; i += 2) {
michael@0 6939 Buf.writeUint16(data.charCodeAt(i + 1));
michael@0 6940 Buf.writeUint16(data.charCodeAt(i));
michael@0 6941 }
michael@0 6942 },
michael@0 6943
michael@0 6944 /**
michael@0 6945 * Read user data, convert to septets, look up relevant characters in a
michael@0 6946 * 7-bit alphabet, and construct string.
michael@0 6947 *
michael@0 6948 * @param length
michael@0 6949 * Number of septets to read (*not* octets)
michael@0 6950 * @param paddingBits
michael@0 6951 * Number of padding bits in the first byte of user data.
michael@0 6952 * @param langIndex
michael@0 6953 * Table index used for normal 7-bit encoded character lookup.
michael@0 6954 * @param langShiftIndex
michael@0 6955 * Table index used for escaped 7-bit encoded character lookup.
michael@0 6956 *
michael@0 6957 * @return a string.
michael@0 6958 */
michael@0 6959 readSeptetsToString: function(length, paddingBits, langIndex, langShiftIndex) {
michael@0 6960 let ret = "";
michael@0 6961 let byteLength = Math.ceil((length * 7 + paddingBits) / 8);
michael@0 6962
michael@0 6963 /**
michael@0 6964 * |<- last byte in header ->|
michael@0 6965 * |<- incompleteBits ->|<- last header septet->|
michael@0 6966 * +===7===|===6===|===5===|===4===|===3===|===2===|===1===|===0===|
michael@0 6967 *
michael@0 6968 * |<- 1st byte in user data ->|
michael@0 6969 * |<- data septet 1 ->|<-paddingBits->|
michael@0 6970 * +===7===|===6===|===5===|===4===|===3===|===2===|===1===|===0===|
michael@0 6971 *
michael@0 6972 * |<- 2nd byte in user data ->|
michael@0 6973 * |<- data spetet 2 ->|<-ds1->|
michael@0 6974 * +===7===|===6===|===5===|===4===|===3===|===2===|===1===|===0===|
michael@0 6975 */
michael@0 6976 let data = 0;
michael@0 6977 let dataBits = 0;
michael@0 6978 if (paddingBits) {
michael@0 6979 data = this.readHexOctet() >> paddingBits;
michael@0 6980 dataBits = 8 - paddingBits;
michael@0 6981 --byteLength;
michael@0 6982 }
michael@0 6983
michael@0 6984 let escapeFound = false;
michael@0 6985 const langTable = PDU_NL_LOCKING_SHIFT_TABLES[langIndex];
michael@0 6986 const langShiftTable = PDU_NL_SINGLE_SHIFT_TABLES[langShiftIndex];
michael@0 6987 do {
michael@0 6988 // Read as much as fits in 32bit word
michael@0 6989 let bytesToRead = Math.min(byteLength, dataBits ? 3 : 4);
michael@0 6990 for (let i = 0; i < bytesToRead; i++) {
michael@0 6991 data |= this.readHexOctet() << dataBits;
michael@0 6992 dataBits += 8;
michael@0 6993 --byteLength;
michael@0 6994 }
michael@0 6995
michael@0 6996 // Consume available full septets
michael@0 6997 for (; dataBits >= 7; dataBits -= 7) {
michael@0 6998 let septet = data & 0x7F;
michael@0 6999 data >>>= 7;
michael@0 7000
michael@0 7001 if (escapeFound) {
michael@0 7002 escapeFound = false;
michael@0 7003 if (septet == PDU_NL_EXTENDED_ESCAPE) {
michael@0 7004 // According to 3GPP TS 23.038, section 6.2.1.1, NOTE 1, "On
michael@0 7005 // receipt of this code, a receiving entity shall display a space
michael@0 7006 // until another extensiion table is defined."
michael@0 7007 ret += " ";
michael@0 7008 } else if (septet == PDU_NL_RESERVED_CONTROL) {
michael@0 7009 // According to 3GPP TS 23.038 B.2, "This code represents a control
michael@0 7010 // character and therefore must not be used for language specific
michael@0 7011 // characters."
michael@0 7012 ret += " ";
michael@0 7013 } else {
michael@0 7014 ret += langShiftTable[septet];
michael@0 7015 }
michael@0 7016 } else if (septet == PDU_NL_EXTENDED_ESCAPE) {
michael@0 7017 escapeFound = true;
michael@0 7018
michael@0 7019 // <escape> is not an effective character
michael@0 7020 --length;
michael@0 7021 } else {
michael@0 7022 ret += langTable[septet];
michael@0 7023 }
michael@0 7024 }
michael@0 7025 } while (byteLength);
michael@0 7026
michael@0 7027 if (ret.length != length) {
michael@0 7028 /**
michael@0 7029 * If num of effective characters does not equal to the length of read
michael@0 7030 * string, cut the tail off. This happens when the last octet of user
michael@0 7031 * data has following layout:
michael@0 7032 *
michael@0 7033 * |<- penultimate octet in user data ->|
michael@0 7034 * |<- data septet N ->|<- dsN-1 ->|
michael@0 7035 * +===7===|===6===|===5===|===4===|===3===|===2===|===1===|===0===|
michael@0 7036 *
michael@0 7037 * |<- last octet in user data ->|
michael@0 7038 * |<- fill bits ->|<-dsN->|
michael@0 7039 * +===7===|===6===|===5===|===4===|===3===|===2===|===1===|===0===|
michael@0 7040 *
michael@0 7041 * The fill bits in the last octet may happen to form a full septet and
michael@0 7042 * be appended at the end of result string.
michael@0 7043 */
michael@0 7044 ret = ret.slice(0, length);
michael@0 7045 }
michael@0 7046 return ret;
michael@0 7047 },
michael@0 7048
michael@0 7049 writeStringAsSeptets: function(message, paddingBits, langIndex, langShiftIndex) {
michael@0 7050 const langTable = PDU_NL_LOCKING_SHIFT_TABLES[langIndex];
michael@0 7051 const langShiftTable = PDU_NL_SINGLE_SHIFT_TABLES[langShiftIndex];
michael@0 7052
michael@0 7053 let dataBits = paddingBits;
michael@0 7054 let data = 0;
michael@0 7055 for (let i = 0; i < message.length; i++) {
michael@0 7056 let c = message.charAt(i);
michael@0 7057 let septet = langTable.indexOf(c);
michael@0 7058 if (septet == PDU_NL_EXTENDED_ESCAPE) {
michael@0 7059 continue;
michael@0 7060 }
michael@0 7061
michael@0 7062 if (septet >= 0) {
michael@0 7063 data |= septet << dataBits;
michael@0 7064 dataBits += 7;
michael@0 7065 } else {
michael@0 7066 septet = langShiftTable.indexOf(c);
michael@0 7067 if (septet == -1) {
michael@0 7068 throw new Error("'" + c + "' is not in 7 bit alphabet "
michael@0 7069 + langIndex + ":" + langShiftIndex + "!");
michael@0 7070 }
michael@0 7071
michael@0 7072 if (septet == PDU_NL_RESERVED_CONTROL) {
michael@0 7073 continue;
michael@0 7074 }
michael@0 7075
michael@0 7076 data |= PDU_NL_EXTENDED_ESCAPE << dataBits;
michael@0 7077 dataBits += 7;
michael@0 7078 data |= septet << dataBits;
michael@0 7079 dataBits += 7;
michael@0 7080 }
michael@0 7081
michael@0 7082 for (; dataBits >= 8; dataBits -= 8) {
michael@0 7083 this.writeHexOctet(data & 0xFF);
michael@0 7084 data >>>= 8;
michael@0 7085 }
michael@0 7086 }
michael@0 7087
michael@0 7088 if (dataBits !== 0) {
michael@0 7089 this.writeHexOctet(data & 0xFF);
michael@0 7090 }
michael@0 7091 },
michael@0 7092
michael@0 7093 /**
michael@0 7094 * Read user data and decode as a UCS2 string.
michael@0 7095 *
michael@0 7096 * @param numOctets
michael@0 7097 * Number of octets to be read as UCS2 string.
michael@0 7098 *
michael@0 7099 * @return a string.
michael@0 7100 */
michael@0 7101 readUCS2String: function(numOctets) {
michael@0 7102 let str = "";
michael@0 7103 let length = numOctets / 2;
michael@0 7104 for (let i = 0; i < length; ++i) {
michael@0 7105 let code = (this.readHexOctet() << 8) | this.readHexOctet();
michael@0 7106 str += String.fromCharCode(code);
michael@0 7107 }
michael@0 7108
michael@0 7109 if (DEBUG) this.context.debug("Read UCS2 string: " + str);
michael@0 7110
michael@0 7111 return str;
michael@0 7112 },
michael@0 7113
michael@0 7114 /**
michael@0 7115 * Write user data as a UCS2 string.
michael@0 7116 *
michael@0 7117 * @param message
michael@0 7118 * Message string to encode as UCS2 in hex-encoded octets.
michael@0 7119 */
michael@0 7120 writeUCS2String: function(message) {
michael@0 7121 for (let i = 0; i < message.length; ++i) {
michael@0 7122 let code = message.charCodeAt(i);
michael@0 7123 this.writeHexOctet((code >> 8) & 0xFF);
michael@0 7124 this.writeHexOctet(code & 0xFF);
michael@0 7125 }
michael@0 7126 },
michael@0 7127
michael@0 7128 /**
michael@0 7129 * Read 1 + UDHL octets and construct user data header.
michael@0 7130 *
michael@0 7131 * @param msg
michael@0 7132 * message object for output.
michael@0 7133 *
michael@0 7134 * @see 3GPP TS 23.040 9.2.3.24
michael@0 7135 */
michael@0 7136 readUserDataHeader: function(msg) {
michael@0 7137 /**
michael@0 7138 * A header object with properties contained in received message.
michael@0 7139 * The properties set include:
michael@0 7140 *
michael@0 7141 * length: totoal length of the header, default 0.
michael@0 7142 * langIndex: used locking shift table index, default
michael@0 7143 * PDU_NL_IDENTIFIER_DEFAULT.
michael@0 7144 * langShiftIndex: used locking shift table index, default
michael@0 7145 * PDU_NL_IDENTIFIER_DEFAULT.
michael@0 7146 *
michael@0 7147 */
michael@0 7148 let header = {
michael@0 7149 length: 0,
michael@0 7150 langIndex: PDU_NL_IDENTIFIER_DEFAULT,
michael@0 7151 langShiftIndex: PDU_NL_IDENTIFIER_DEFAULT
michael@0 7152 };
michael@0 7153
michael@0 7154 header.length = this.readHexOctet();
michael@0 7155 if (DEBUG) this.context.debug("Read UDH length: " + header.length);
michael@0 7156
michael@0 7157 let dataAvailable = header.length;
michael@0 7158 while (dataAvailable >= 2) {
michael@0 7159 let id = this.readHexOctet();
michael@0 7160 let length = this.readHexOctet();
michael@0 7161 if (DEBUG) this.context.debug("Read UDH id: " + id + ", length: " + length);
michael@0 7162
michael@0 7163 dataAvailable -= 2;
michael@0 7164
michael@0 7165 switch (id) {
michael@0 7166 case PDU_IEI_CONCATENATED_SHORT_MESSAGES_8BIT: {
michael@0 7167 let ref = this.readHexOctet();
michael@0 7168 let max = this.readHexOctet();
michael@0 7169 let seq = this.readHexOctet();
michael@0 7170 dataAvailable -= 3;
michael@0 7171 if (max && seq && (seq <= max)) {
michael@0 7172 header.segmentRef = ref;
michael@0 7173 header.segmentMaxSeq = max;
michael@0 7174 header.segmentSeq = seq;
michael@0 7175 }
michael@0 7176 break;
michael@0 7177 }
michael@0 7178 case PDU_IEI_APPLICATION_PORT_ADDRESSING_SCHEME_8BIT: {
michael@0 7179 let dstp = this.readHexOctet();
michael@0 7180 let orip = this.readHexOctet();
michael@0 7181 dataAvailable -= 2;
michael@0 7182 if ((dstp < PDU_APA_RESERVED_8BIT_PORTS)
michael@0 7183 || (orip < PDU_APA_RESERVED_8BIT_PORTS)) {
michael@0 7184 // 3GPP TS 23.040 clause 9.2.3.24.3: "A receiving entity shall
michael@0 7185 // ignore any information element where the value of the
michael@0 7186 // Information-Element-Data is Reserved or not supported"
michael@0 7187 break;
michael@0 7188 }
michael@0 7189 header.destinationPort = dstp;
michael@0 7190 header.originatorPort = orip;
michael@0 7191 break;
michael@0 7192 }
michael@0 7193 case PDU_IEI_APPLICATION_PORT_ADDRESSING_SCHEME_16BIT: {
michael@0 7194 let dstp = (this.readHexOctet() << 8) | this.readHexOctet();
michael@0 7195 let orip = (this.readHexOctet() << 8) | this.readHexOctet();
michael@0 7196 dataAvailable -= 4;
michael@0 7197 // 3GPP TS 23.040 clause 9.2.3.24.4: "A receiving entity shall
michael@0 7198 // ignore any information element where the value of the
michael@0 7199 // Information-Element-Data is Reserved or not supported"
michael@0 7200 if ((dstp < PDU_APA_VALID_16BIT_PORTS)
michael@0 7201 && (orip < PDU_APA_VALID_16BIT_PORTS)) {
michael@0 7202 header.destinationPort = dstp;
michael@0 7203 header.originatorPort = orip;
michael@0 7204 }
michael@0 7205 break;
michael@0 7206 }
michael@0 7207 case PDU_IEI_CONCATENATED_SHORT_MESSAGES_16BIT: {
michael@0 7208 let ref = (this.readHexOctet() << 8) | this.readHexOctet();
michael@0 7209 let max = this.readHexOctet();
michael@0 7210 let seq = this.readHexOctet();
michael@0 7211 dataAvailable -= 4;
michael@0 7212 if (max && seq && (seq <= max)) {
michael@0 7213 header.segmentRef = ref;
michael@0 7214 header.segmentMaxSeq = max;
michael@0 7215 header.segmentSeq = seq;
michael@0 7216 }
michael@0 7217 break;
michael@0 7218 }
michael@0 7219 case PDU_IEI_NATIONAL_LANGUAGE_SINGLE_SHIFT:
michael@0 7220 let langShiftIndex = this.readHexOctet();
michael@0 7221 --dataAvailable;
michael@0 7222 if (langShiftIndex < PDU_NL_SINGLE_SHIFT_TABLES.length) {
michael@0 7223 header.langShiftIndex = langShiftIndex;
michael@0 7224 }
michael@0 7225 break;
michael@0 7226 case PDU_IEI_NATIONAL_LANGUAGE_LOCKING_SHIFT:
michael@0 7227 let langIndex = this.readHexOctet();
michael@0 7228 --dataAvailable;
michael@0 7229 if (langIndex < PDU_NL_LOCKING_SHIFT_TABLES.length) {
michael@0 7230 header.langIndex = langIndex;
michael@0 7231 }
michael@0 7232 break;
michael@0 7233 case PDU_IEI_SPECIAL_SMS_MESSAGE_INDICATION:
michael@0 7234 let msgInd = this.readHexOctet() & 0xFF;
michael@0 7235 let msgCount = this.readHexOctet();
michael@0 7236 dataAvailable -= 2;
michael@0 7237
michael@0 7238
michael@0 7239 /*
michael@0 7240 * TS 23.040 V6.8.1 Sec 9.2.3.24.2
michael@0 7241 * bits 1 0 : basic message indication type
michael@0 7242 * bits 4 3 2 : extended message indication type
michael@0 7243 * bits 6 5 : Profile id
michael@0 7244 * bit 7 : storage type
michael@0 7245 */
michael@0 7246 let storeType = msgInd & PDU_MWI_STORE_TYPE_BIT;
michael@0 7247 let mwi = msg.mwi;
michael@0 7248 if (!mwi) {
michael@0 7249 mwi = msg.mwi = {};
michael@0 7250 }
michael@0 7251
michael@0 7252 if (storeType == PDU_MWI_STORE_TYPE_STORE) {
michael@0 7253 // Store message because TP_UDH indicates so, note this may override
michael@0 7254 // the setting in DCS, but that is expected
michael@0 7255 mwi.discard = false;
michael@0 7256 } else if (mwi.discard === undefined) {
michael@0 7257 // storeType == PDU_MWI_STORE_TYPE_DISCARD
michael@0 7258 // only override mwi.discard here if it hasn't already been set
michael@0 7259 mwi.discard = true;
michael@0 7260 }
michael@0 7261
michael@0 7262 mwi.msgCount = msgCount & 0xFF;
michael@0 7263 mwi.active = mwi.msgCount > 0;
michael@0 7264
michael@0 7265 if (DEBUG) {
michael@0 7266 this.context.debug("MWI in TP_UDH received: " + JSON.stringify(mwi));
michael@0 7267 }
michael@0 7268
michael@0 7269 break;
michael@0 7270 default:
michael@0 7271 if (DEBUG) {
michael@0 7272 this.context.debug("readUserDataHeader: unsupported IEI(" + id +
michael@0 7273 "), " + length + " bytes.");
michael@0 7274 }
michael@0 7275
michael@0 7276 // Read out unsupported data
michael@0 7277 if (length) {
michael@0 7278 let octets;
michael@0 7279 if (DEBUG) octets = new Uint8Array(length);
michael@0 7280
michael@0 7281 for (let i = 0; i < length; i++) {
michael@0 7282 let octet = this.readHexOctet();
michael@0 7283 if (DEBUG) octets[i] = octet;
michael@0 7284 }
michael@0 7285 dataAvailable -= length;
michael@0 7286
michael@0 7287 if (DEBUG) {
michael@0 7288 this.context.debug("readUserDataHeader: " + Array.slice(octets));
michael@0 7289 }
michael@0 7290 }
michael@0 7291 break;
michael@0 7292 }
michael@0 7293 }
michael@0 7294
michael@0 7295 if (dataAvailable !== 0) {
michael@0 7296 throw new Error("Illegal user data header found!");
michael@0 7297 }
michael@0 7298
michael@0 7299 msg.header = header;
michael@0 7300 },
michael@0 7301
michael@0 7302 /**
michael@0 7303 * Write out user data header.
michael@0 7304 *
michael@0 7305 * @param options
michael@0 7306 * Options containing information for user data header write-out. The
michael@0 7307 * `userDataHeaderLength` property must be correctly pre-calculated.
michael@0 7308 */
michael@0 7309 writeUserDataHeader: function(options) {
michael@0 7310 this.writeHexOctet(options.userDataHeaderLength);
michael@0 7311
michael@0 7312 if (options.segmentMaxSeq > 1) {
michael@0 7313 if (options.segmentRef16Bit) {
michael@0 7314 this.writeHexOctet(PDU_IEI_CONCATENATED_SHORT_MESSAGES_16BIT);
michael@0 7315 this.writeHexOctet(4);
michael@0 7316 this.writeHexOctet((options.segmentRef >> 8) & 0xFF);
michael@0 7317 } else {
michael@0 7318 this.writeHexOctet(PDU_IEI_CONCATENATED_SHORT_MESSAGES_8BIT);
michael@0 7319 this.writeHexOctet(3);
michael@0 7320 }
michael@0 7321 this.writeHexOctet(options.segmentRef & 0xFF);
michael@0 7322 this.writeHexOctet(options.segmentMaxSeq & 0xFF);
michael@0 7323 this.writeHexOctet(options.segmentSeq & 0xFF);
michael@0 7324 }
michael@0 7325
michael@0 7326 if (options.dcs == PDU_DCS_MSG_CODING_7BITS_ALPHABET) {
michael@0 7327 if (options.langIndex != PDU_NL_IDENTIFIER_DEFAULT) {
michael@0 7328 this.writeHexOctet(PDU_IEI_NATIONAL_LANGUAGE_LOCKING_SHIFT);
michael@0 7329 this.writeHexOctet(1);
michael@0 7330 this.writeHexOctet(options.langIndex);
michael@0 7331 }
michael@0 7332
michael@0 7333 if (options.langShiftIndex != PDU_NL_IDENTIFIER_DEFAULT) {
michael@0 7334 this.writeHexOctet(PDU_IEI_NATIONAL_LANGUAGE_SINGLE_SHIFT);
michael@0 7335 this.writeHexOctet(1);
michael@0 7336 this.writeHexOctet(options.langShiftIndex);
michael@0 7337 }
michael@0 7338 }
michael@0 7339 },
michael@0 7340
michael@0 7341 /**
michael@0 7342 * Read SM-TL Address.
michael@0 7343 *
michael@0 7344 * @param len
michael@0 7345 * Length of useful semi-octets within the Address-Value field. For
michael@0 7346 * example, the lenth of "12345" should be 5, and 4 for "1234".
michael@0 7347 *
michael@0 7348 * @see 3GPP TS 23.040 9.1.2.5
michael@0 7349 */
michael@0 7350 readAddress: function(len) {
michael@0 7351 // Address Length
michael@0 7352 if (!len || (len < 0)) {
michael@0 7353 if (DEBUG) {
michael@0 7354 this.context.debug("PDU error: invalid sender address length: " + len);
michael@0 7355 }
michael@0 7356 return null;
michael@0 7357 }
michael@0 7358 if (len % 2 == 1) {
michael@0 7359 len += 1;
michael@0 7360 }
michael@0 7361 if (DEBUG) this.context.debug("PDU: Going to read address: " + len);
michael@0 7362
michael@0 7363 // Type-of-Address
michael@0 7364 let toa = this.readHexOctet();
michael@0 7365 let addr = "";
michael@0 7366
michael@0 7367 if ((toa & 0xF0) == PDU_TOA_ALPHANUMERIC) {
michael@0 7368 addr = this.readSeptetsToString(Math.floor(len * 4 / 7), 0,
michael@0 7369 PDU_NL_IDENTIFIER_DEFAULT , PDU_NL_IDENTIFIER_DEFAULT );
michael@0 7370 return addr;
michael@0 7371 }
michael@0 7372 addr = this.readSwappedNibbleBcdString(len / 2);
michael@0 7373 if (addr.length <= 0) {
michael@0 7374 if (DEBUG) this.context.debug("PDU error: no number provided");
michael@0 7375 return null;
michael@0 7376 }
michael@0 7377 if ((toa & 0xF0) == (PDU_TOA_INTERNATIONAL)) {
michael@0 7378 addr = '+' + addr;
michael@0 7379 }
michael@0 7380
michael@0 7381 return addr;
michael@0 7382 },
michael@0 7383
michael@0 7384 /**
michael@0 7385 * Read TP-Protocol-Indicator(TP-PID).
michael@0 7386 *
michael@0 7387 * @param msg
michael@0 7388 * message object for output.
michael@0 7389 *
michael@0 7390 * @see 3GPP TS 23.040 9.2.3.9
michael@0 7391 */
michael@0 7392 readProtocolIndicator: function(msg) {
michael@0 7393 // `The MS shall interpret reserved, obsolete, or unsupported values as the
michael@0 7394 // value 00000000 but shall store them exactly as received.`
michael@0 7395 msg.pid = this.readHexOctet();
michael@0 7396
michael@0 7397 msg.epid = msg.pid;
michael@0 7398 switch (msg.epid & 0xC0) {
michael@0 7399 case 0x40:
michael@0 7400 // Bit 7..0 = 01xxxxxx
michael@0 7401 switch (msg.epid) {
michael@0 7402 case PDU_PID_SHORT_MESSAGE_TYPE_0:
michael@0 7403 case PDU_PID_ANSI_136_R_DATA:
michael@0 7404 case PDU_PID_USIM_DATA_DOWNLOAD:
michael@0 7405 return;
michael@0 7406 }
michael@0 7407 break;
michael@0 7408 }
michael@0 7409
michael@0 7410 msg.epid = PDU_PID_DEFAULT;
michael@0 7411 },
michael@0 7412
michael@0 7413 /**
michael@0 7414 * Read TP-Data-Coding-Scheme(TP-DCS)
michael@0 7415 *
michael@0 7416 * @param msg
michael@0 7417 * message object for output.
michael@0 7418 *
michael@0 7419 * @see 3GPP TS 23.040 9.2.3.10, 3GPP TS 23.038 4.
michael@0 7420 */
michael@0 7421 readDataCodingScheme: function(msg) {
michael@0 7422 let dcs = this.readHexOctet();
michael@0 7423 if (DEBUG) this.context.debug("PDU: read SMS dcs: " + dcs);
michael@0 7424
michael@0 7425 // No message class by default.
michael@0 7426 let messageClass = PDU_DCS_MSG_CLASS_NORMAL;
michael@0 7427 // 7 bit is the default fallback encoding.
michael@0 7428 let encoding = PDU_DCS_MSG_CODING_7BITS_ALPHABET;
michael@0 7429 switch (dcs & PDU_DCS_CODING_GROUP_BITS) {
michael@0 7430 case 0x40: // bits 7..4 = 01xx
michael@0 7431 case 0x50:
michael@0 7432 case 0x60:
michael@0 7433 case 0x70:
michael@0 7434 // Bit 5..0 are coded exactly the same as Group 00xx
michael@0 7435 case 0x00: // bits 7..4 = 00xx
michael@0 7436 case 0x10:
michael@0 7437 case 0x20:
michael@0 7438 case 0x30:
michael@0 7439 if (dcs & 0x10) {
michael@0 7440 messageClass = dcs & PDU_DCS_MSG_CLASS_BITS;
michael@0 7441 }
michael@0 7442 switch (dcs & 0x0C) {
michael@0 7443 case 0x4:
michael@0 7444 encoding = PDU_DCS_MSG_CODING_8BITS_ALPHABET;
michael@0 7445 break;
michael@0 7446 case 0x8:
michael@0 7447 encoding = PDU_DCS_MSG_CODING_16BITS_ALPHABET;
michael@0 7448 break;
michael@0 7449 }
michael@0 7450 break;
michael@0 7451
michael@0 7452 case 0xE0: // bits 7..4 = 1110
michael@0 7453 encoding = PDU_DCS_MSG_CODING_16BITS_ALPHABET;
michael@0 7454 // Bit 3..0 are coded exactly the same as Message Waiting Indication
michael@0 7455 // Group 1101.
michael@0 7456 // Fall through.
michael@0 7457 case 0xC0: // bits 7..4 = 1100
michael@0 7458 case 0xD0: // bits 7..4 = 1101
michael@0 7459 // Indiciates voicemail indicator set or clear
michael@0 7460 let active = (dcs & PDU_DCS_MWI_ACTIVE_BITS) == PDU_DCS_MWI_ACTIVE_VALUE;
michael@0 7461
michael@0 7462 // If TP-UDH is present, these values will be overwritten
michael@0 7463 switch (dcs & PDU_DCS_MWI_TYPE_BITS) {
michael@0 7464 case PDU_DCS_MWI_TYPE_VOICEMAIL:
michael@0 7465 let mwi = msg.mwi;
michael@0 7466 if (!mwi) {
michael@0 7467 mwi = msg.mwi = {};
michael@0 7468 }
michael@0 7469
michael@0 7470 mwi.active = active;
michael@0 7471 mwi.discard = (dcs & PDU_DCS_CODING_GROUP_BITS) == 0xC0;
michael@0 7472 mwi.msgCount = active ? GECKO_VOICEMAIL_MESSAGE_COUNT_UNKNOWN : 0;
michael@0 7473
michael@0 7474 if (DEBUG) {
michael@0 7475 this.context.debug("MWI in DCS received for voicemail: " +
michael@0 7476 JSON.stringify(mwi));
michael@0 7477 }
michael@0 7478 break;
michael@0 7479 case PDU_DCS_MWI_TYPE_FAX:
michael@0 7480 if (DEBUG) this.context.debug("MWI in DCS received for fax");
michael@0 7481 break;
michael@0 7482 case PDU_DCS_MWI_TYPE_EMAIL:
michael@0 7483 if (DEBUG) this.context.debug("MWI in DCS received for email");
michael@0 7484 break;
michael@0 7485 default:
michael@0 7486 if (DEBUG) this.context.debug("MWI in DCS received for \"other\"");
michael@0 7487 break;
michael@0 7488 }
michael@0 7489 break;
michael@0 7490
michael@0 7491 case 0xF0: // bits 7..4 = 1111
michael@0 7492 if (dcs & 0x04) {
michael@0 7493 encoding = PDU_DCS_MSG_CODING_8BITS_ALPHABET;
michael@0 7494 }
michael@0 7495 messageClass = dcs & PDU_DCS_MSG_CLASS_BITS;
michael@0 7496 break;
michael@0 7497
michael@0 7498 default:
michael@0 7499 // Falling back to default encoding.
michael@0 7500 break;
michael@0 7501 }
michael@0 7502
michael@0 7503 msg.dcs = dcs;
michael@0 7504 msg.encoding = encoding;
michael@0 7505 msg.messageClass = GECKO_SMS_MESSAGE_CLASSES[messageClass];
michael@0 7506
michael@0 7507 if (DEBUG) this.context.debug("PDU: message encoding is " + encoding + " bit.");
michael@0 7508 },
michael@0 7509
michael@0 7510 /**
michael@0 7511 * Read GSM TP-Service-Centre-Time-Stamp(TP-SCTS).
michael@0 7512 *
michael@0 7513 * @see 3GPP TS 23.040 9.2.3.11
michael@0 7514 */
michael@0 7515 readTimestamp: function() {
michael@0 7516 let year = this.readSwappedNibbleBcdNum(1) + PDU_TIMESTAMP_YEAR_OFFSET;
michael@0 7517 let month = this.readSwappedNibbleBcdNum(1) - 1;
michael@0 7518 let day = this.readSwappedNibbleBcdNum(1);
michael@0 7519 let hour = this.readSwappedNibbleBcdNum(1);
michael@0 7520 let minute = this.readSwappedNibbleBcdNum(1);
michael@0 7521 let second = this.readSwappedNibbleBcdNum(1);
michael@0 7522 let timestamp = Date.UTC(year, month, day, hour, minute, second);
michael@0 7523
michael@0 7524 // If the most significant bit of the least significant nibble is 1,
michael@0 7525 // the timezone offset is negative (fourth bit from the right => 0x08):
michael@0 7526 // localtime = UTC + tzOffset
michael@0 7527 // therefore
michael@0 7528 // UTC = localtime - tzOffset
michael@0 7529 let tzOctet = this.readHexOctet();
michael@0 7530 let tzOffset = this.octetToBCD(tzOctet & ~0x08) * 15 * 60 * 1000;
michael@0 7531 tzOffset = (tzOctet & 0x08) ? -tzOffset : tzOffset;
michael@0 7532 timestamp -= tzOffset;
michael@0 7533
michael@0 7534 return timestamp;
michael@0 7535 },
michael@0 7536
michael@0 7537 /**
michael@0 7538 * Write GSM TP-Service-Centre-Time-Stamp(TP-SCTS).
michael@0 7539 *
michael@0 7540 * @see 3GPP TS 23.040 9.2.3.11
michael@0 7541 */
michael@0 7542 writeTimestamp: function(date) {
michael@0 7543 this.writeSwappedNibbleBCDNum(date.getFullYear() - PDU_TIMESTAMP_YEAR_OFFSET);
michael@0 7544
michael@0 7545 // The value returned by getMonth() is an integer between 0 and 11.
michael@0 7546 // 0 is corresponds to January, 1 to February, and so on.
michael@0 7547 this.writeSwappedNibbleBCDNum(date.getMonth() + 1);
michael@0 7548 this.writeSwappedNibbleBCDNum(date.getDate());
michael@0 7549 this.writeSwappedNibbleBCDNum(date.getHours());
michael@0 7550 this.writeSwappedNibbleBCDNum(date.getMinutes());
michael@0 7551 this.writeSwappedNibbleBCDNum(date.getSeconds());
michael@0 7552
michael@0 7553 // the value returned by getTimezoneOffset() is the difference,
michael@0 7554 // in minutes, between UTC and local time.
michael@0 7555 // For example, if your time zone is UTC+10 (Australian Eastern Standard Time),
michael@0 7556 // -600 will be returned.
michael@0 7557 // In TS 23.040 9.2.3.11, the Time Zone field of TP-SCTS indicates
michael@0 7558 // the different between the local time and GMT.
michael@0 7559 // And expressed in quarters of an hours. (so need to divid by 15)
michael@0 7560 let zone = date.getTimezoneOffset() / 15;
michael@0 7561 let octet = this.BCDToOctet(zone);
michael@0 7562
michael@0 7563 // the bit3 of the Time Zone field represents the algebraic sign.
michael@0 7564 // (0: positive, 1: negative).
michael@0 7565 // For example, if the time zone is -0800 GMT,
michael@0 7566 // 480 will be returned by getTimezoneOffset().
michael@0 7567 // In this case, need to mark sign bit as 1. => 0x08
michael@0 7568 if (zone > 0) {
michael@0 7569 octet = octet | 0x08;
michael@0 7570 }
michael@0 7571 this.writeHexOctet(octet);
michael@0 7572 },
michael@0 7573
michael@0 7574 /**
michael@0 7575 * User data can be 7 bit (default alphabet) data, 8 bit data, or 16 bit
michael@0 7576 * (UCS2) data.
michael@0 7577 *
michael@0 7578 * @param msg
michael@0 7579 * message object for output.
michael@0 7580 * @param length
michael@0 7581 * length of user data to read in octets.
michael@0 7582 */
michael@0 7583 readUserData: function(msg, length) {
michael@0 7584 if (DEBUG) {
michael@0 7585 this.context.debug("Reading " + length + " bytes of user data.");
michael@0 7586 }
michael@0 7587
michael@0 7588 let paddingBits = 0;
michael@0 7589 if (msg.udhi) {
michael@0 7590 this.readUserDataHeader(msg);
michael@0 7591
michael@0 7592 if (msg.encoding == PDU_DCS_MSG_CODING_7BITS_ALPHABET) {
michael@0 7593 let headerBits = (msg.header.length + 1) * 8;
michael@0 7594 let headerSeptets = Math.ceil(headerBits / 7);
michael@0 7595
michael@0 7596 length -= headerSeptets;
michael@0 7597 paddingBits = headerSeptets * 7 - headerBits;
michael@0 7598 } else {
michael@0 7599 length -= (msg.header.length + 1);
michael@0 7600 }
michael@0 7601 }
michael@0 7602
michael@0 7603 if (DEBUG) {
michael@0 7604 this.context.debug("After header, " + length + " septets left of user data");
michael@0 7605 }
michael@0 7606
michael@0 7607 msg.body = null;
michael@0 7608 msg.data = null;
michael@0 7609 switch (msg.encoding) {
michael@0 7610 case PDU_DCS_MSG_CODING_7BITS_ALPHABET:
michael@0 7611 // 7 bit encoding allows 140 octets, which means 160 characters
michael@0 7612 // ((140x8) / 7 = 160 chars)
michael@0 7613 if (length > PDU_MAX_USER_DATA_7BIT) {
michael@0 7614 if (DEBUG) {
michael@0 7615 this.context.debug("PDU error: user data is too long: " + length);
michael@0 7616 }
michael@0 7617 break;
michael@0 7618 }
michael@0 7619
michael@0 7620 let langIndex = msg.udhi ? msg.header.langIndex : PDU_NL_IDENTIFIER_DEFAULT;
michael@0 7621 let langShiftIndex = msg.udhi ? msg.header.langShiftIndex : PDU_NL_IDENTIFIER_DEFAULT;
michael@0 7622 msg.body = this.readSeptetsToString(length, paddingBits, langIndex,
michael@0 7623 langShiftIndex);
michael@0 7624 break;
michael@0 7625 case PDU_DCS_MSG_CODING_8BITS_ALPHABET:
michael@0 7626 msg.data = this.readHexOctetArray(length);
michael@0 7627 break;
michael@0 7628 case PDU_DCS_MSG_CODING_16BITS_ALPHABET:
michael@0 7629 msg.body = this.readUCS2String(length);
michael@0 7630 break;
michael@0 7631 }
michael@0 7632 },
michael@0 7633
michael@0 7634 /**
michael@0 7635 * Read extra parameters if TP-PI is set.
michael@0 7636 *
michael@0 7637 * @param msg
michael@0 7638 * message object for output.
michael@0 7639 */
michael@0 7640 readExtraParams: function(msg) {
michael@0 7641 // Because each PDU octet is converted to two UCS2 char2, we should always
michael@0 7642 // get even messageStringLength in this#_processReceivedSms(). So, we'll
michael@0 7643 // always need two delimitors at the end.
michael@0 7644 if (this.context.Buf.getReadAvailable() <= 4) {
michael@0 7645 return;
michael@0 7646 }
michael@0 7647
michael@0 7648 // TP-Parameter-Indicator
michael@0 7649 let pi;
michael@0 7650 do {
michael@0 7651 // `The most significant bit in octet 1 and any other TP-PI octets which
michael@0 7652 // may be added later is reserved as an extension bit which when set to a
michael@0 7653 // 1 shall indicate that another TP-PI octet follows immediately
michael@0 7654 // afterwards.` ~ 3GPP TS 23.040 9.2.3.27
michael@0 7655 pi = this.readHexOctet();
michael@0 7656 } while (pi & PDU_PI_EXTENSION);
michael@0 7657
michael@0 7658 // `If the TP-UDL bit is set to "1" but the TP-DCS bit is set to "0" then
michael@0 7659 // the receiving entity shall for TP-DCS assume a value of 0x00, i.e. the
michael@0 7660 // 7bit default alphabet.` ~ 3GPP 23.040 9.2.3.27
michael@0 7661 msg.dcs = 0;
michael@0 7662 msg.encoding = PDU_DCS_MSG_CODING_7BITS_ALPHABET;
michael@0 7663
michael@0 7664 // TP-Protocol-Identifier
michael@0 7665 if (pi & PDU_PI_PROTOCOL_IDENTIFIER) {
michael@0 7666 this.readProtocolIndicator(msg);
michael@0 7667 }
michael@0 7668 // TP-Data-Coding-Scheme
michael@0 7669 if (pi & PDU_PI_DATA_CODING_SCHEME) {
michael@0 7670 this.readDataCodingScheme(msg);
michael@0 7671 }
michael@0 7672 // TP-User-Data-Length
michael@0 7673 if (pi & PDU_PI_USER_DATA_LENGTH) {
michael@0 7674 let userDataLength = this.readHexOctet();
michael@0 7675 this.readUserData(msg, userDataLength);
michael@0 7676 }
michael@0 7677 },
michael@0 7678
michael@0 7679 /**
michael@0 7680 * Read and decode a PDU-encoded message from the stream.
michael@0 7681 *
michael@0 7682 * TODO: add some basic sanity checks like:
michael@0 7683 * - do we have the minimum number of chars available
michael@0 7684 */
michael@0 7685 readMessage: function() {
michael@0 7686 // An empty message object. This gets filled below and then returned.
michael@0 7687 let msg = {
michael@0 7688 // D:DELIVER, DR:DELIVER-REPORT, S:SUBMIT, SR:SUBMIT-REPORT,
michael@0 7689 // ST:STATUS-REPORT, C:COMMAND
michael@0 7690 // M:Mandatory, O:Optional, X:Unavailable
michael@0 7691 // D DR S SR ST C
michael@0 7692 SMSC: null, // M M M M M M
michael@0 7693 mti: null, // M M M M M M
michael@0 7694 udhi: null, // M M O M M M
michael@0 7695 sender: null, // M X X X X X
michael@0 7696 recipient: null, // X X M X M M
michael@0 7697 pid: null, // M O M O O M
michael@0 7698 epid: null, // M O M O O M
michael@0 7699 dcs: null, // M O M O O X
michael@0 7700 mwi: null, // O O O O O O
michael@0 7701 replace: false, // O O O O O O
michael@0 7702 header: null, // M M O M M M
michael@0 7703 body: null, // M O M O O O
michael@0 7704 data: null, // M O M O O O
michael@0 7705 sentTimestamp: null, // M X X X X X
michael@0 7706 status: null, // X X X X M X
michael@0 7707 scts: null, // X X X M M X
michael@0 7708 dt: null, // X X X X M X
michael@0 7709 };
michael@0 7710
michael@0 7711 // SMSC info
michael@0 7712 let smscLength = this.readHexOctet();
michael@0 7713 if (smscLength > 0) {
michael@0 7714 let smscTypeOfAddress = this.readHexOctet();
michael@0 7715 // Subtract the type-of-address octet we just read from the length.
michael@0 7716 msg.SMSC = this.readSwappedNibbleBcdString(smscLength - 1);
michael@0 7717 if ((smscTypeOfAddress >> 4) == (PDU_TOA_INTERNATIONAL >> 4)) {
michael@0 7718 msg.SMSC = '+' + msg.SMSC;
michael@0 7719 }
michael@0 7720 }
michael@0 7721
michael@0 7722 // First octet of this SMS-DELIVER or SMS-SUBMIT message
michael@0 7723 let firstOctet = this.readHexOctet();
michael@0 7724 // Message Type Indicator
michael@0 7725 msg.mti = firstOctet & 0x03;
michael@0 7726 // User data header indicator
michael@0 7727 msg.udhi = firstOctet & PDU_UDHI;
michael@0 7728
michael@0 7729 switch (msg.mti) {
michael@0 7730 case PDU_MTI_SMS_RESERVED:
michael@0 7731 // `If an MS receives a TPDU with a "Reserved" value in the TP-MTI it
michael@0 7732 // shall process the message as if it were an "SMS-DELIVER" but store
michael@0 7733 // the message exactly as received.` ~ 3GPP TS 23.040 9.2.3.1
michael@0 7734 case PDU_MTI_SMS_DELIVER:
michael@0 7735 return this.readDeliverMessage(msg);
michael@0 7736 case PDU_MTI_SMS_STATUS_REPORT:
michael@0 7737 return this.readStatusReportMessage(msg);
michael@0 7738 default:
michael@0 7739 return null;
michael@0 7740 }
michael@0 7741 },
michael@0 7742
michael@0 7743 /**
michael@0 7744 * Helper for processing received SMS parcel data.
michael@0 7745 *
michael@0 7746 * @param length
michael@0 7747 * Length of SMS string in the incoming parcel.
michael@0 7748 *
michael@0 7749 * @return Message parsed or null for invalid message.
michael@0 7750 */
michael@0 7751 processReceivedSms: function(length) {
michael@0 7752 if (!length) {
michael@0 7753 if (DEBUG) this.context.debug("Received empty SMS!");
michael@0 7754 return [null, PDU_FCS_UNSPECIFIED];
michael@0 7755 }
michael@0 7756
michael@0 7757 let Buf = this.context.Buf;
michael@0 7758
michael@0 7759 // An SMS is a string, but we won't read it as such, so let's read the
michael@0 7760 // string length and then defer to PDU parsing helper.
michael@0 7761 let messageStringLength = Buf.readInt32();
michael@0 7762 if (DEBUG) this.context.debug("Got new SMS, length " + messageStringLength);
michael@0 7763 let message = this.readMessage();
michael@0 7764 if (DEBUG) this.context.debug("Got new SMS: " + JSON.stringify(message));
michael@0 7765
michael@0 7766 // Read string delimiters. See Buf.readString().
michael@0 7767 Buf.readStringDelimiter(length);
michael@0 7768
michael@0 7769 // Determine result
michael@0 7770 if (!message) {
michael@0 7771 return [null, PDU_FCS_UNSPECIFIED];
michael@0 7772 }
michael@0 7773
michael@0 7774 if (message.epid == PDU_PID_SHORT_MESSAGE_TYPE_0) {
michael@0 7775 // `A short message type 0 indicates that the ME must acknowledge receipt
michael@0 7776 // of the short message but shall discard its contents.` ~ 3GPP TS 23.040
michael@0 7777 // 9.2.3.9
michael@0 7778 return [null, PDU_FCS_OK];
michael@0 7779 }
michael@0 7780
michael@0 7781 if (message.messageClass == GECKO_SMS_MESSAGE_CLASSES[PDU_DCS_MSG_CLASS_2]) {
michael@0 7782 let RIL = this.context.RIL;
michael@0 7783 switch (message.epid) {
michael@0 7784 case PDU_PID_ANSI_136_R_DATA:
michael@0 7785 case PDU_PID_USIM_DATA_DOWNLOAD:
michael@0 7786 let ICCUtilsHelper = this.context.ICCUtilsHelper;
michael@0 7787 if (ICCUtilsHelper.isICCServiceAvailable("DATA_DOWNLOAD_SMS_PP")) {
michael@0 7788 // `If the service "data download via SMS Point-to-Point" is
michael@0 7789 // allocated and activated in the (U)SIM Service Table, ... then the
michael@0 7790 // ME shall pass the message transparently to the UICC using the
michael@0 7791 // ENVELOPE (SMS-PP DOWNLOAD).` ~ 3GPP TS 31.111 7.1.1.1
michael@0 7792 RIL.dataDownloadViaSMSPP(message);
michael@0 7793
michael@0 7794 // `the ME shall not display the message, or alert the user of a
michael@0 7795 // short message waiting.` ~ 3GPP TS 31.111 7.1.1.1
michael@0 7796 return [null, PDU_FCS_RESERVED];
michael@0 7797 }
michael@0 7798
michael@0 7799 // If the service "data download via SMS-PP" is not available in the
michael@0 7800 // (U)SIM Service Table, ..., then the ME shall store the message in
michael@0 7801 // EFsms in accordance with TS 31.102` ~ 3GPP TS 31.111 7.1.1.1
michael@0 7802
michael@0 7803 // Fall through.
michael@0 7804 default:
michael@0 7805 RIL.writeSmsToSIM(message);
michael@0 7806 break;
michael@0 7807 }
michael@0 7808 }
michael@0 7809
michael@0 7810 // TODO: Bug 739143: B2G SMS: Support SMS Storage Full event
michael@0 7811 if ((message.messageClass != GECKO_SMS_MESSAGE_CLASSES[PDU_DCS_MSG_CLASS_0]) && !true) {
michael@0 7812 // `When a mobile terminated message is class 0..., the MS shall display
michael@0 7813 // the message immediately and send a ACK to the SC ..., irrespective of
michael@0 7814 // whether there is memory available in the (U)SIM or ME.` ~ 3GPP 23.038
michael@0 7815 // clause 4.
michael@0 7816
michael@0 7817 if (message.messageClass == GECKO_SMS_MESSAGE_CLASSES[PDU_DCS_MSG_CLASS_2]) {
michael@0 7818 // `If all the short message storage at the MS is already in use, the
michael@0 7819 // MS shall return "memory capacity exceeded".` ~ 3GPP 23.038 clause 4.
michael@0 7820 return [null, PDU_FCS_MEMORY_CAPACITY_EXCEEDED];
michael@0 7821 }
michael@0 7822
michael@0 7823 return [null, PDU_FCS_UNSPECIFIED];
michael@0 7824 }
michael@0 7825
michael@0 7826 return [message, PDU_FCS_OK];
michael@0 7827 },
michael@0 7828
michael@0 7829 /**
michael@0 7830 * Read and decode a SMS-DELIVER PDU.
michael@0 7831 *
michael@0 7832 * @param msg
michael@0 7833 * message object for output.
michael@0 7834 */
michael@0 7835 readDeliverMessage: function(msg) {
michael@0 7836 // - Sender Address info -
michael@0 7837 let senderAddressLength = this.readHexOctet();
michael@0 7838 msg.sender = this.readAddress(senderAddressLength);
michael@0 7839 // - TP-Protocolo-Identifier -
michael@0 7840 this.readProtocolIndicator(msg);
michael@0 7841 // - TP-Data-Coding-Scheme -
michael@0 7842 this.readDataCodingScheme(msg);
michael@0 7843 // - TP-Service-Center-Time-Stamp -
michael@0 7844 msg.sentTimestamp = this.readTimestamp();
michael@0 7845 // - TP-User-Data-Length -
michael@0 7846 let userDataLength = this.readHexOctet();
michael@0 7847
michael@0 7848 // - TP-User-Data -
michael@0 7849 if (userDataLength > 0) {
michael@0 7850 this.readUserData(msg, userDataLength);
michael@0 7851 }
michael@0 7852
michael@0 7853 return msg;
michael@0 7854 },
michael@0 7855
michael@0 7856 /**
michael@0 7857 * Read and decode a SMS-STATUS-REPORT PDU.
michael@0 7858 *
michael@0 7859 * @param msg
michael@0 7860 * message object for output.
michael@0 7861 */
michael@0 7862 readStatusReportMessage: function(msg) {
michael@0 7863 // TP-Message-Reference
michael@0 7864 msg.messageRef = this.readHexOctet();
michael@0 7865 // TP-Recipient-Address
michael@0 7866 let recipientAddressLength = this.readHexOctet();
michael@0 7867 msg.recipient = this.readAddress(recipientAddressLength);
michael@0 7868 // TP-Service-Centre-Time-Stamp
michael@0 7869 msg.scts = this.readTimestamp();
michael@0 7870 // TP-Discharge-Time
michael@0 7871 msg.dt = this.readTimestamp();
michael@0 7872 // TP-Status
michael@0 7873 msg.status = this.readHexOctet();
michael@0 7874
michael@0 7875 this.readExtraParams(msg);
michael@0 7876
michael@0 7877 return msg;
michael@0 7878 },
michael@0 7879
michael@0 7880 /**
michael@0 7881 * Serialize a SMS-SUBMIT PDU message and write it to the output stream.
michael@0 7882 *
michael@0 7883 * This method expects that a data coding scheme has been chosen already
michael@0 7884 * and that the length of the user data payload in that encoding is known,
michael@0 7885 * too. Both go hand in hand together anyway.
michael@0 7886 *
michael@0 7887 * @param address
michael@0 7888 * String containing the address (number) of the SMS receiver
michael@0 7889 * @param userData
michael@0 7890 * String containing the message to be sent as user data
michael@0 7891 * @param dcs
michael@0 7892 * Data coding scheme. One of the PDU_DCS_MSG_CODING_*BITS_ALPHABET
michael@0 7893 * constants.
michael@0 7894 * @param userDataHeaderLength
michael@0 7895 * Length of embedded user data header, in bytes. The whole header
michael@0 7896 * size will be userDataHeaderLength + 1; 0 for no header.
michael@0 7897 * @param encodedBodyLength
michael@0 7898 * Length of the user data when encoded with the given DCS. For UCS2,
michael@0 7899 * in bytes; for 7-bit, in septets.
michael@0 7900 * @param langIndex
michael@0 7901 * Table index used for normal 7-bit encoded character lookup.
michael@0 7902 * @param langShiftIndex
michael@0 7903 * Table index used for escaped 7-bit encoded character lookup.
michael@0 7904 * @param requestStatusReport
michael@0 7905 * Request status report.
michael@0 7906 */
michael@0 7907 writeMessage: function(options) {
michael@0 7908 if (DEBUG) {
michael@0 7909 this.context.debug("writeMessage: " + JSON.stringify(options));
michael@0 7910 }
michael@0 7911 let Buf = this.context.Buf;
michael@0 7912 let address = options.number;
michael@0 7913 let body = options.body;
michael@0 7914 let dcs = options.dcs;
michael@0 7915 let userDataHeaderLength = options.userDataHeaderLength;
michael@0 7916 let encodedBodyLength = options.encodedBodyLength;
michael@0 7917 let langIndex = options.langIndex;
michael@0 7918 let langShiftIndex = options.langShiftIndex;
michael@0 7919
michael@0 7920 // SMS-SUBMIT Format:
michael@0 7921 //
michael@0 7922 // PDU Type - 1 octet
michael@0 7923 // Message Reference - 1 octet
michael@0 7924 // DA - Destination Address - 2 to 12 octets
michael@0 7925 // PID - Protocol Identifier - 1 octet
michael@0 7926 // DCS - Data Coding Scheme - 1 octet
michael@0 7927 // VP - Validity Period - 0, 1 or 7 octets
michael@0 7928 // UDL - User Data Length - 1 octet
michael@0 7929 // UD - User Data - 140 octets
michael@0 7930
michael@0 7931 let addressFormat = PDU_TOA_ISDN; // 81
michael@0 7932 if (address[0] == '+') {
michael@0 7933 addressFormat = PDU_TOA_INTERNATIONAL | PDU_TOA_ISDN; // 91
michael@0 7934 address = address.substring(1);
michael@0 7935 }
michael@0 7936 //TODO validity is unsupported for now
michael@0 7937 let validity = 0;
michael@0 7938
michael@0 7939 let headerOctets = (userDataHeaderLength ? userDataHeaderLength + 1 : 0);
michael@0 7940 let paddingBits;
michael@0 7941 let userDataLengthInSeptets;
michael@0 7942 let userDataLengthInOctets;
michael@0 7943 if (dcs == PDU_DCS_MSG_CODING_7BITS_ALPHABET) {
michael@0 7944 let headerSeptets = Math.ceil(headerOctets * 8 / 7);
michael@0 7945 userDataLengthInSeptets = headerSeptets + encodedBodyLength;
michael@0 7946 userDataLengthInOctets = Math.ceil(userDataLengthInSeptets * 7 / 8);
michael@0 7947 paddingBits = headerSeptets * 7 - headerOctets * 8;
michael@0 7948 } else {
michael@0 7949 userDataLengthInOctets = headerOctets + encodedBodyLength;
michael@0 7950 paddingBits = 0;
michael@0 7951 }
michael@0 7952
michael@0 7953 let pduOctetLength = 4 + // PDU Type, Message Ref, address length + format
michael@0 7954 Math.ceil(address.length / 2) +
michael@0 7955 3 + // PID, DCS, UDL
michael@0 7956 userDataLengthInOctets;
michael@0 7957 if (validity) {
michael@0 7958 //TODO: add more to pduOctetLength
michael@0 7959 }
michael@0 7960
michael@0 7961 // Start the string. Since octets are represented in hex, we will need
michael@0 7962 // twice as many characters as octets.
michael@0 7963 Buf.writeInt32(pduOctetLength * 2);
michael@0 7964
michael@0 7965 // - PDU-TYPE-
michael@0 7966
michael@0 7967 // +--------+----------+---------+---------+--------+---------+
michael@0 7968 // | RP (1) | UDHI (1) | SRR (1) | VPF (2) | RD (1) | MTI (2) |
michael@0 7969 // +--------+----------+---------+---------+--------+---------+
michael@0 7970 // RP: 0 Reply path parameter is not set
michael@0 7971 // 1 Reply path parameter is set
michael@0 7972 // UDHI: 0 The UD Field contains only the short message
michael@0 7973 // 1 The beginning of the UD field contains a header in addition
michael@0 7974 // of the short message
michael@0 7975 // SRR: 0 A status report is not requested
michael@0 7976 // 1 A status report is requested
michael@0 7977 // VPF: bit4 bit3
michael@0 7978 // 0 0 VP field is not present
michael@0 7979 // 0 1 Reserved
michael@0 7980 // 1 0 VP field present an integer represented (relative)
michael@0 7981 // 1 1 VP field present a semi-octet represented (absolute)
michael@0 7982 // RD: Instruct the SMSC to accept(0) or reject(1) an SMS-SUBMIT
michael@0 7983 // for a short message still held in the SMSC which has the same
michael@0 7984 // MR and DA as a previously submitted short message from the
michael@0 7985 // same OA
michael@0 7986 // MTI: bit1 bit0 Message Type
michael@0 7987 // 0 0 SMS-DELIVER (SMSC ==> MS)
michael@0 7988 // 0 1 SMS-SUBMIT (MS ==> SMSC)
michael@0 7989
michael@0 7990 // PDU type. MTI is set to SMS-SUBMIT
michael@0 7991 let firstOctet = PDU_MTI_SMS_SUBMIT;
michael@0 7992
michael@0 7993 // Status-Report-Request
michael@0 7994 if (options.requestStatusReport) {
michael@0 7995 firstOctet |= PDU_SRI_SRR;
michael@0 7996 }
michael@0 7997
michael@0 7998 // Validity period
michael@0 7999 if (validity) {
michael@0 8000 //TODO: not supported yet, OR with one of PDU_VPF_*
michael@0 8001 }
michael@0 8002 // User data header indicator
michael@0 8003 if (headerOctets) {
michael@0 8004 firstOctet |= PDU_UDHI;
michael@0 8005 }
michael@0 8006 this.writeHexOctet(firstOctet);
michael@0 8007
michael@0 8008 // Message reference 00
michael@0 8009 this.writeHexOctet(0x00);
michael@0 8010
michael@0 8011 // - Destination Address -
michael@0 8012 this.writeHexOctet(address.length);
michael@0 8013 this.writeHexOctet(addressFormat);
michael@0 8014 this.writeSwappedNibbleBCD(address);
michael@0 8015
michael@0 8016 // - Protocol Identifier -
michael@0 8017 this.writeHexOctet(0x00);
michael@0 8018
michael@0 8019 // - Data coding scheme -
michael@0 8020 // For now it assumes bits 7..4 = 1111 except for the 16 bits use case
michael@0 8021 this.writeHexOctet(dcs);
michael@0 8022
michael@0 8023 // - Validity Period -
michael@0 8024 if (validity) {
michael@0 8025 this.writeHexOctet(validity);
michael@0 8026 }
michael@0 8027
michael@0 8028 // - User Data -
michael@0 8029 if (dcs == PDU_DCS_MSG_CODING_7BITS_ALPHABET) {
michael@0 8030 this.writeHexOctet(userDataLengthInSeptets);
michael@0 8031 } else {
michael@0 8032 this.writeHexOctet(userDataLengthInOctets);
michael@0 8033 }
michael@0 8034
michael@0 8035 if (headerOctets) {
michael@0 8036 this.writeUserDataHeader(options);
michael@0 8037 }
michael@0 8038
michael@0 8039 switch (dcs) {
michael@0 8040 case PDU_DCS_MSG_CODING_7BITS_ALPHABET:
michael@0 8041 this.writeStringAsSeptets(body, paddingBits, langIndex, langShiftIndex);
michael@0 8042 break;
michael@0 8043 case PDU_DCS_MSG_CODING_8BITS_ALPHABET:
michael@0 8044 // Unsupported.
michael@0 8045 break;
michael@0 8046 case PDU_DCS_MSG_CODING_16BITS_ALPHABET:
michael@0 8047 this.writeUCS2String(body);
michael@0 8048 break;
michael@0 8049 }
michael@0 8050
michael@0 8051 // End of the string. The string length is always even by definition, so
michael@0 8052 // we write two \0 delimiters.
michael@0 8053 Buf.writeUint16(0);
michael@0 8054 Buf.writeUint16(0);
michael@0 8055 },
michael@0 8056
michael@0 8057 /**
michael@0 8058 * Read GSM CBS message serial number.
michael@0 8059 *
michael@0 8060 * @param msg
michael@0 8061 * message object for output.
michael@0 8062 *
michael@0 8063 * @see 3GPP TS 23.041 section 9.4.1.2.1
michael@0 8064 */
michael@0 8065 readCbSerialNumber: function(msg) {
michael@0 8066 let Buf = this.context.Buf;
michael@0 8067 msg.serial = Buf.readUint8() << 8 | Buf.readUint8();
michael@0 8068 msg.geographicalScope = (msg.serial >>> 14) & 0x03;
michael@0 8069 msg.messageCode = (msg.serial >>> 4) & 0x03FF;
michael@0 8070 msg.updateNumber = msg.serial & 0x0F;
michael@0 8071 },
michael@0 8072
michael@0 8073 /**
michael@0 8074 * Read GSM CBS message message identifier.
michael@0 8075 *
michael@0 8076 * @param msg
michael@0 8077 * message object for output.
michael@0 8078 *
michael@0 8079 * @see 3GPP TS 23.041 section 9.4.1.2.2
michael@0 8080 */
michael@0 8081 readCbMessageIdentifier: function(msg) {
michael@0 8082 let Buf = this.context.Buf;
michael@0 8083 msg.messageId = Buf.readUint8() << 8 | Buf.readUint8();
michael@0 8084
michael@0 8085 if ((msg.format != CB_FORMAT_ETWS)
michael@0 8086 && (msg.messageId >= CB_GSM_MESSAGEID_ETWS_BEGIN)
michael@0 8087 && (msg.messageId <= CB_GSM_MESSAGEID_ETWS_END)) {
michael@0 8088 // `In the case of transmitting CBS message for ETWS, a part of
michael@0 8089 // Message Code can be used to command mobile terminals to activate
michael@0 8090 // emergency user alert and message popup in order to alert the users.`
michael@0 8091 msg.etws = {
michael@0 8092 emergencyUserAlert: msg.messageCode & 0x0200 ? true : false,
michael@0 8093 popup: msg.messageCode & 0x0100 ? true : false
michael@0 8094 };
michael@0 8095
michael@0 8096 let warningType = msg.messageId - CB_GSM_MESSAGEID_ETWS_BEGIN;
michael@0 8097 if (warningType < CB_ETWS_WARNING_TYPE_NAMES.length) {
michael@0 8098 msg.etws.warningType = warningType;
michael@0 8099 }
michael@0 8100 }
michael@0 8101 },
michael@0 8102
michael@0 8103 /**
michael@0 8104 * Read CBS Data Coding Scheme.
michael@0 8105 *
michael@0 8106 * @param msg
michael@0 8107 * message object for output.
michael@0 8108 *
michael@0 8109 * @see 3GPP TS 23.038 section 5.
michael@0 8110 */
michael@0 8111 readCbDataCodingScheme: function(msg) {
michael@0 8112 let dcs = this.context.Buf.readUint8();
michael@0 8113 if (DEBUG) this.context.debug("PDU: read CBS dcs: " + dcs);
michael@0 8114
michael@0 8115 let language = null, hasLanguageIndicator = false;
michael@0 8116 // `Any reserved codings shall be assumed to be the GSM 7bit default
michael@0 8117 // alphabet.`
michael@0 8118 let encoding = PDU_DCS_MSG_CODING_7BITS_ALPHABET;
michael@0 8119 let messageClass = PDU_DCS_MSG_CLASS_NORMAL;
michael@0 8120
michael@0 8121 switch (dcs & PDU_DCS_CODING_GROUP_BITS) {
michael@0 8122 case 0x00: // 0000
michael@0 8123 language = CB_DCS_LANG_GROUP_1[dcs & 0x0F];
michael@0 8124 break;
michael@0 8125
michael@0 8126 case 0x10: // 0001
michael@0 8127 switch (dcs & 0x0F) {
michael@0 8128 case 0x00:
michael@0 8129 hasLanguageIndicator = true;
michael@0 8130 break;
michael@0 8131 case 0x01:
michael@0 8132 encoding = PDU_DCS_MSG_CODING_16BITS_ALPHABET;
michael@0 8133 hasLanguageIndicator = true;
michael@0 8134 break;
michael@0 8135 }
michael@0 8136 break;
michael@0 8137
michael@0 8138 case 0x20: // 0010
michael@0 8139 language = CB_DCS_LANG_GROUP_2[dcs & 0x0F];
michael@0 8140 break;
michael@0 8141
michael@0 8142 case 0x40: // 01xx
michael@0 8143 case 0x50:
michael@0 8144 //case 0x60: Text Compression, not supported
michael@0 8145 //case 0x70: Text Compression, not supported
michael@0 8146 case 0x90: // 1001
michael@0 8147 encoding = (dcs & 0x0C);
michael@0 8148 if (encoding == 0x0C) {
michael@0 8149 encoding = PDU_DCS_MSG_CODING_7BITS_ALPHABET;
michael@0 8150 }
michael@0 8151 messageClass = (dcs & PDU_DCS_MSG_CLASS_BITS);
michael@0 8152 break;
michael@0 8153
michael@0 8154 case 0xF0:
michael@0 8155 encoding = (dcs & 0x04) ? PDU_DCS_MSG_CODING_8BITS_ALPHABET
michael@0 8156 : PDU_DCS_MSG_CODING_7BITS_ALPHABET;
michael@0 8157 switch(dcs & PDU_DCS_MSG_CLASS_BITS) {
michael@0 8158 case 0x01: messageClass = PDU_DCS_MSG_CLASS_USER_1; break;
michael@0 8159 case 0x02: messageClass = PDU_DCS_MSG_CLASS_USER_2; break;
michael@0 8160 case 0x03: messageClass = PDU_DCS_MSG_CLASS_3; break;
michael@0 8161 }
michael@0 8162 break;
michael@0 8163
michael@0 8164 case 0x30: // 0011 (Reserved)
michael@0 8165 case 0x80: // 1000 (Reserved)
michael@0 8166 case 0xA0: // 1010..1100 (Reserved)
michael@0 8167 case 0xB0:
michael@0 8168 case 0xC0:
michael@0 8169 break;
michael@0 8170
michael@0 8171 default:
michael@0 8172 throw new Error("Unsupported CBS data coding scheme: " + dcs);
michael@0 8173 }
michael@0 8174
michael@0 8175 msg.dcs = dcs;
michael@0 8176 msg.encoding = encoding;
michael@0 8177 msg.language = language;
michael@0 8178 msg.messageClass = GECKO_SMS_MESSAGE_CLASSES[messageClass];
michael@0 8179 msg.hasLanguageIndicator = hasLanguageIndicator;
michael@0 8180 },
michael@0 8181
michael@0 8182 /**
michael@0 8183 * Read GSM CBS message page parameter.
michael@0 8184 *
michael@0 8185 * @param msg
michael@0 8186 * message object for output.
michael@0 8187 *
michael@0 8188 * @see 3GPP TS 23.041 section 9.4.1.2.4
michael@0 8189 */
michael@0 8190 readCbPageParameter: function(msg) {
michael@0 8191 let octet = this.context.Buf.readUint8();
michael@0 8192 msg.pageIndex = (octet >>> 4) & 0x0F;
michael@0 8193 msg.numPages = octet & 0x0F;
michael@0 8194 if (!msg.pageIndex || !msg.numPages) {
michael@0 8195 // `If a mobile receives the code 0000 in either the first field or the
michael@0 8196 // second field then it shall treat the CBS message exactly the same as a
michael@0 8197 // CBS message with page parameter 0001 0001 (i.e. a single page message).`
michael@0 8198 msg.pageIndex = msg.numPages = 1;
michael@0 8199 }
michael@0 8200 },
michael@0 8201
michael@0 8202 /**
michael@0 8203 * Read ETWS Primary Notification message warning type.
michael@0 8204 *
michael@0 8205 * @param msg
michael@0 8206 * message object for output.
michael@0 8207 *
michael@0 8208 * @see 3GPP TS 23.041 section 9.3.24
michael@0 8209 */
michael@0 8210 readCbWarningType: function(msg) {
michael@0 8211 let Buf = this.context.Buf;
michael@0 8212 let word = Buf.readUint8() << 8 | Buf.readUint8();
michael@0 8213 msg.etws = {
michael@0 8214 warningType: (word >>> 9) & 0x7F,
michael@0 8215 popup: word & 0x80 ? true : false,
michael@0 8216 emergencyUserAlert: word & 0x100 ? true : false
michael@0 8217 };
michael@0 8218 },
michael@0 8219
michael@0 8220 /**
michael@0 8221 * Read CBS-Message-Information-Page
michael@0 8222 *
michael@0 8223 * @param msg
michael@0 8224 * message object for output.
michael@0 8225 * @param length
michael@0 8226 * length of cell broadcast data to read in octets.
michael@0 8227 *
michael@0 8228 * @see 3GPP TS 23.041 section 9.3.19
michael@0 8229 */
michael@0 8230 readGsmCbData: function(msg, length) {
michael@0 8231 let Buf = this.context.Buf;
michael@0 8232 let bufAdapter = {
michael@0 8233 context: this.context,
michael@0 8234 readHexOctet: function() {
michael@0 8235 return Buf.readUint8();
michael@0 8236 }
michael@0 8237 };
michael@0 8238
michael@0 8239 msg.body = null;
michael@0 8240 msg.data = null;
michael@0 8241 switch (msg.encoding) {
michael@0 8242 case PDU_DCS_MSG_CODING_7BITS_ALPHABET:
michael@0 8243 msg.body = this.readSeptetsToString.call(bufAdapter,
michael@0 8244 (length * 8 / 7), 0,
michael@0 8245 PDU_NL_IDENTIFIER_DEFAULT,
michael@0 8246 PDU_NL_IDENTIFIER_DEFAULT);
michael@0 8247 if (msg.hasLanguageIndicator) {
michael@0 8248 msg.language = msg.body.substring(0, 2);
michael@0 8249 msg.body = msg.body.substring(3);
michael@0 8250 }
michael@0 8251 break;
michael@0 8252
michael@0 8253 case PDU_DCS_MSG_CODING_8BITS_ALPHABET:
michael@0 8254 msg.data = Buf.readUint8Array(length);
michael@0 8255 break;
michael@0 8256
michael@0 8257 case PDU_DCS_MSG_CODING_16BITS_ALPHABET:
michael@0 8258 if (msg.hasLanguageIndicator) {
michael@0 8259 msg.language = this.readSeptetsToString.call(bufAdapter, 2, 0,
michael@0 8260 PDU_NL_IDENTIFIER_DEFAULT,
michael@0 8261 PDU_NL_IDENTIFIER_DEFAULT);
michael@0 8262 length -= 2;
michael@0 8263 }
michael@0 8264 msg.body = this.readUCS2String.call(bufAdapter, length);
michael@0 8265 break;
michael@0 8266 }
michael@0 8267 },
michael@0 8268
michael@0 8269 /**
michael@0 8270 * Read Cell GSM/ETWS/UMTS Broadcast Message.
michael@0 8271 *
michael@0 8272 * @param pduLength
michael@0 8273 * total length of the incoming PDU in octets.
michael@0 8274 */
michael@0 8275 readCbMessage: function(pduLength) {
michael@0 8276 // Validity GSM ETWS UMTS
michael@0 8277 let msg = {
michael@0 8278 // Internally used in ril_worker:
michael@0 8279 serial: null, // O O O
michael@0 8280 updateNumber: null, // O O O
michael@0 8281 format: null, // O O O
michael@0 8282 dcs: 0x0F, // O X O
michael@0 8283 encoding: PDU_DCS_MSG_CODING_7BITS_ALPHABET, // O X O
michael@0 8284 hasLanguageIndicator: false, // O X O
michael@0 8285 data: null, // O X O
michael@0 8286 body: null, // O X O
michael@0 8287 pageIndex: 1, // O X X
michael@0 8288 numPages: 1, // O X X
michael@0 8289
michael@0 8290 // DOM attributes:
michael@0 8291 geographicalScope: null, // O O O
michael@0 8292 messageCode: null, // O O O
michael@0 8293 messageId: null, // O O O
michael@0 8294 language: null, // O X O
michael@0 8295 fullBody: null, // O X O
michael@0 8296 fullData: null, // O X O
michael@0 8297 messageClass: GECKO_SMS_MESSAGE_CLASSES[PDU_DCS_MSG_CLASS_NORMAL], // O x O
michael@0 8298 etws: null // ? O ?
michael@0 8299 /*{
michael@0 8300 warningType: null, // X O X
michael@0 8301 popup: false, // X O X
michael@0 8302 emergencyUserAlert: false, // X O X
michael@0 8303 }*/
michael@0 8304 };
michael@0 8305
michael@0 8306 if (pduLength <= CB_MESSAGE_SIZE_ETWS) {
michael@0 8307 msg.format = CB_FORMAT_ETWS;
michael@0 8308 return this.readEtwsCbMessage(msg);
michael@0 8309 }
michael@0 8310
michael@0 8311 if (pduLength <= CB_MESSAGE_SIZE_GSM) {
michael@0 8312 msg.format = CB_FORMAT_GSM;
michael@0 8313 return this.readGsmCbMessage(msg, pduLength);
michael@0 8314 }
michael@0 8315
michael@0 8316 return null;
michael@0 8317 },
michael@0 8318
michael@0 8319 /**
michael@0 8320 * Read GSM Cell Broadcast Message.
michael@0 8321 *
michael@0 8322 * @param msg
michael@0 8323 * message object for output.
michael@0 8324 * @param pduLength
michael@0 8325 * total length of the incomint PDU in octets.
michael@0 8326 *
michael@0 8327 * @see 3GPP TS 23.041 clause 9.4.1.2
michael@0 8328 */
michael@0 8329 readGsmCbMessage: function(msg, pduLength) {
michael@0 8330 this.readCbSerialNumber(msg);
michael@0 8331 this.readCbMessageIdentifier(msg);
michael@0 8332 this.readCbDataCodingScheme(msg);
michael@0 8333 this.readCbPageParameter(msg);
michael@0 8334
michael@0 8335 // GSM CB message header takes 6 octets.
michael@0 8336 this.readGsmCbData(msg, pduLength - 6);
michael@0 8337
michael@0 8338 return msg;
michael@0 8339 },
michael@0 8340
michael@0 8341 /**
michael@0 8342 * Read ETWS Primary Notification Message.
michael@0 8343 *
michael@0 8344 * @param msg
michael@0 8345 * message object for output.
michael@0 8346 *
michael@0 8347 * @see 3GPP TS 23.041 clause 9.4.1.3
michael@0 8348 */
michael@0 8349 readEtwsCbMessage: function(msg) {
michael@0 8350 this.readCbSerialNumber(msg);
michael@0 8351 this.readCbMessageIdentifier(msg);
michael@0 8352 this.readCbWarningType(msg);
michael@0 8353
michael@0 8354 // Octet 7..56 is Warning Security Information. However, according to
michael@0 8355 // section 9.4.1.3.6, `The UE shall ignore this parameter.` So we just skip
michael@0 8356 // processing it here.
michael@0 8357
michael@0 8358 return msg;
michael@0 8359 },
michael@0 8360
michael@0 8361 /**
michael@0 8362 * Read network name.
michael@0 8363 *
michael@0 8364 * @param len Length of the information element.
michael@0 8365 * @return
michael@0 8366 * {
michael@0 8367 * networkName: network name.
michael@0 8368 * shouldIncludeCi: Should Country's initials included in text string.
michael@0 8369 * }
michael@0 8370 * @see TS 24.008 clause 10.5.3.5a.
michael@0 8371 */
michael@0 8372 readNetworkName: function(len) {
michael@0 8373 // According to TS 24.008 Sec. 10.5.3.5a, the first octet is:
michael@0 8374 // bit 8: must be 1.
michael@0 8375 // bit 5-7: Text encoding.
michael@0 8376 // 000 - GSM default alphabet.
michael@0 8377 // 001 - UCS2 (16 bit).
michael@0 8378 // else - reserved.
michael@0 8379 // bit 4: MS should add the letters for Country's Initials and a space
michael@0 8380 // to the text string if this bit is true.
michael@0 8381 // bit 1-3: number of spare bits in last octet.
michael@0 8382
michael@0 8383 let codingInfo = this.readHexOctet();
michael@0 8384 if (!(codingInfo & 0x80)) {
michael@0 8385 return null;
michael@0 8386 }
michael@0 8387
michael@0 8388 let textEncoding = (codingInfo & 0x70) >> 4;
michael@0 8389 let shouldIncludeCountryInitials = !!(codingInfo & 0x08);
michael@0 8390 let spareBits = codingInfo & 0x07;
michael@0 8391 let resultString;
michael@0 8392
michael@0 8393 switch (textEncoding) {
michael@0 8394 case 0:
michael@0 8395 // GSM Default alphabet.
michael@0 8396 resultString = this.readSeptetsToString(
michael@0 8397 ((len - 1) * 8 - spareBits) / 7, 0,
michael@0 8398 PDU_NL_IDENTIFIER_DEFAULT,
michael@0 8399 PDU_NL_IDENTIFIER_DEFAULT);
michael@0 8400 break;
michael@0 8401 case 1:
michael@0 8402 // UCS2 encoded.
michael@0 8403 resultString = this.readUCS2String(len - 1);
michael@0 8404 break;
michael@0 8405 default:
michael@0 8406 // Not an available text coding.
michael@0 8407 return null;
michael@0 8408 }
michael@0 8409
michael@0 8410 // TODO - Bug 820286: According to shouldIncludeCountryInitials, add
michael@0 8411 // country initials to the resulting string.
michael@0 8412 return resultString;
michael@0 8413 }
michael@0 8414 };
michael@0 8415
michael@0 8416 /**
michael@0 8417 * Provide buffer with bitwise read/write function so make encoding/decoding easier.
michael@0 8418 */
michael@0 8419 function BitBufferHelperObject(/* unused */aContext) {
michael@0 8420 this.readBuffer = [];
michael@0 8421 this.writeBuffer = [];
michael@0 8422 }
michael@0 8423 BitBufferHelperObject.prototype = {
michael@0 8424 readCache: 0,
michael@0 8425 readCacheSize: 0,
michael@0 8426 readBuffer: null,
michael@0 8427 readIndex: 0,
michael@0 8428 writeCache: 0,
michael@0 8429 writeCacheSize: 0,
michael@0 8430 writeBuffer: null,
michael@0 8431
michael@0 8432 // Max length is 32 because we use integer as read/write cache.
michael@0 8433 // All read/write functions are implemented based on bitwise operation.
michael@0 8434 readBits: function(length) {
michael@0 8435 if (length <= 0 || length > 32) {
michael@0 8436 return null;
michael@0 8437 }
michael@0 8438
michael@0 8439 if (length > this.readCacheSize) {
michael@0 8440 let bytesToRead = Math.ceil((length - this.readCacheSize) / 8);
michael@0 8441 for(let i = 0; i < bytesToRead; i++) {
michael@0 8442 this.readCache = (this.readCache << 8) | (this.readBuffer[this.readIndex++] & 0xFF);
michael@0 8443 this.readCacheSize += 8;
michael@0 8444 }
michael@0 8445 }
michael@0 8446
michael@0 8447 let bitOffset = (this.readCacheSize - length),
michael@0 8448 resultMask = (1 << length) - 1,
michael@0 8449 result = 0;
michael@0 8450
michael@0 8451 result = (this.readCache >> bitOffset) & resultMask;
michael@0 8452 this.readCacheSize -= length;
michael@0 8453
michael@0 8454 return result;
michael@0 8455 },
michael@0 8456
michael@0 8457 backwardReadPilot: function(length) {
michael@0 8458 if (length <= 0) {
michael@0 8459 return;
michael@0 8460 }
michael@0 8461
michael@0 8462 // Zero-based position.
michael@0 8463 let bitIndexToRead = this.readIndex * 8 - this.readCacheSize - length;
michael@0 8464
michael@0 8465 if (bitIndexToRead < 0) {
michael@0 8466 return;
michael@0 8467 }
michael@0 8468
michael@0 8469 // Update readIndex, readCache, readCacheSize accordingly.
michael@0 8470 let readBits = bitIndexToRead % 8;
michael@0 8471 this.readIndex = Math.floor(bitIndexToRead / 8) + ((readBits) ? 1 : 0);
michael@0 8472 this.readCache = (readBits) ? this.readBuffer[this.readIndex - 1] : 0;
michael@0 8473 this.readCacheSize = (readBits) ? (8 - readBits) : 0;
michael@0 8474 },
michael@0 8475
michael@0 8476 writeBits: function(value, length) {
michael@0 8477 if (length <= 0 || length > 32) {
michael@0 8478 return;
michael@0 8479 }
michael@0 8480
michael@0 8481 let totalLength = length + this.writeCacheSize;
michael@0 8482
michael@0 8483 // 8-byte cache not full
michael@0 8484 if (totalLength < 8) {
michael@0 8485 let valueMask = (1 << length) - 1;
michael@0 8486 this.writeCache = (this.writeCache << length) | (value & valueMask);
michael@0 8487 this.writeCacheSize += length;
michael@0 8488 return;
michael@0 8489 }
michael@0 8490
michael@0 8491 // Deal with unaligned part
michael@0 8492 if (this.writeCacheSize) {
michael@0 8493 let mergeLength = 8 - this.writeCacheSize,
michael@0 8494 valueMask = (1 << mergeLength) - 1;
michael@0 8495
michael@0 8496 this.writeCache = (this.writeCache << mergeLength) | ((value >> (length - mergeLength)) & valueMask);
michael@0 8497 this.writeBuffer.push(this.writeCache & 0xFF);
michael@0 8498 length -= mergeLength;
michael@0 8499 }
michael@0 8500
michael@0 8501 // Aligned part, just copy
michael@0 8502 while (length >= 8) {
michael@0 8503 length -= 8;
michael@0 8504 this.writeBuffer.push((value >> length) & 0xFF);
michael@0 8505 }
michael@0 8506
michael@0 8507 // Rest part is saved into cache
michael@0 8508 this.writeCacheSize = length;
michael@0 8509 this.writeCache = value & ((1 << length) - 1);
michael@0 8510
michael@0 8511 return;
michael@0 8512 },
michael@0 8513
michael@0 8514 // Drop what still in read cache and goto next 8-byte alignment.
michael@0 8515 // There might be a better naming.
michael@0 8516 nextOctetAlign: function() {
michael@0 8517 this.readCache = 0;
michael@0 8518 this.readCacheSize = 0;
michael@0 8519 },
michael@0 8520
michael@0 8521 // Flush current write cache to Buf with padding 0s.
michael@0 8522 // There might be a better naming.
michael@0 8523 flushWithPadding: function() {
michael@0 8524 if (this.writeCacheSize) {
michael@0 8525 this.writeBuffer.push(this.writeCache << (8 - this.writeCacheSize));
michael@0 8526 }
michael@0 8527 this.writeCache = 0;
michael@0 8528 this.writeCacheSize = 0;
michael@0 8529 },
michael@0 8530
michael@0 8531 startWrite: function(dataBuffer) {
michael@0 8532 this.writeBuffer = dataBuffer;
michael@0 8533 this.writeCache = 0;
michael@0 8534 this.writeCacheSize = 0;
michael@0 8535 },
michael@0 8536
michael@0 8537 startRead: function(dataBuffer) {
michael@0 8538 this.readBuffer = dataBuffer;
michael@0 8539 this.readCache = 0;
michael@0 8540 this.readCacheSize = 0;
michael@0 8541 this.readIndex = 0;
michael@0 8542 },
michael@0 8543
michael@0 8544 getWriteBufferSize: function() {
michael@0 8545 return this.writeBuffer.length;
michael@0 8546 },
michael@0 8547
michael@0 8548 overwriteWriteBuffer: function(position, data) {
michael@0 8549 let writeLength = data.length;
michael@0 8550 if (writeLength + position >= this.writeBuffer.length) {
michael@0 8551 writeLength = this.writeBuffer.length - position;
michael@0 8552 }
michael@0 8553 for (let i = 0; i < writeLength; i++) {
michael@0 8554 this.writeBuffer[i + position] = data[i];
michael@0 8555 }
michael@0 8556 }
michael@0 8557 };
michael@0 8558
michael@0 8559 /**
michael@0 8560 * Helper for CDMA PDU
michael@0 8561 *
michael@0 8562 * Currently, some function are shared with GsmPDUHelper, they should be
michael@0 8563 * moved from GsmPDUHelper to a common object shared among GsmPDUHelper and
michael@0 8564 * CdmaPDUHelper.
michael@0 8565 */
michael@0 8566 function CdmaPDUHelperObject(aContext) {
michael@0 8567 this.context = aContext;
michael@0 8568 }
michael@0 8569 CdmaPDUHelperObject.prototype = {
michael@0 8570 context: null,
michael@0 8571
michael@0 8572 // 1..........C
michael@0 8573 // Only "1234567890*#" is defined in C.S0005-D v2.0
michael@0 8574 dtmfChars: ".1234567890*#...",
michael@0 8575
michael@0 8576 /**
michael@0 8577 * Entry point for SMS encoding, the options object is made compatible
michael@0 8578 * with existing writeMessage() of GsmPDUHelper, but less key is used.
michael@0 8579 *
michael@0 8580 * Current used key in options:
michael@0 8581 * @param number
michael@0 8582 * String containing the address (number) of the SMS receiver
michael@0 8583 * @param body
michael@0 8584 * String containing the message to be sent, segmented part
michael@0 8585 * @param dcs
michael@0 8586 * Data coding scheme. One of the PDU_DCS_MSG_CODING_*BITS_ALPHABET
michael@0 8587 * constants.
michael@0 8588 * @param encodedBodyLength
michael@0 8589 * Length of the user data when encoded with the given DCS. For UCS2,
michael@0 8590 * in bytes; for 7-bit, in septets.
michael@0 8591 * @param requestStatusReport
michael@0 8592 * Request status report.
michael@0 8593 * @param segmentRef
michael@0 8594 * Reference number of concatenated SMS message
michael@0 8595 * @param segmentMaxSeq
michael@0 8596 * Total number of concatenated SMS message
michael@0 8597 * @param segmentSeq
michael@0 8598 * Sequence number of concatenated SMS message
michael@0 8599 */
michael@0 8600 writeMessage: function(options) {
michael@0 8601 if (DEBUG) {
michael@0 8602 this.context.debug("cdma_writeMessage: " + JSON.stringify(options));
michael@0 8603 }
michael@0 8604
michael@0 8605 // Get encoding
michael@0 8606 options.encoding = this.gsmDcsToCdmaEncoding(options.dcs);
michael@0 8607
michael@0 8608 // Common Header
michael@0 8609 if (options.segmentMaxSeq > 1) {
michael@0 8610 this.writeInt(PDU_CDMA_MSG_TELESERIVCIE_ID_WEMT);
michael@0 8611 } else {
michael@0 8612 this.writeInt(PDU_CDMA_MSG_TELESERIVCIE_ID_SMS);
michael@0 8613 }
michael@0 8614
michael@0 8615 this.writeInt(0);
michael@0 8616 this.writeInt(PDU_CDMA_MSG_CATEGORY_UNSPEC);
michael@0 8617
michael@0 8618 // Just fill out address info in byte, rild will encap them for us
michael@0 8619 let addrInfo = this.encodeAddr(options.number);
michael@0 8620 this.writeByte(addrInfo.digitMode);
michael@0 8621 this.writeByte(addrInfo.numberMode);
michael@0 8622 this.writeByte(addrInfo.numberType);
michael@0 8623 this.writeByte(addrInfo.numberPlan);
michael@0 8624 this.writeByte(addrInfo.address.length);
michael@0 8625 for (let i = 0; i < addrInfo.address.length; i++) {
michael@0 8626 this.writeByte(addrInfo.address[i]);
michael@0 8627 }
michael@0 8628
michael@0 8629 // Subaddress, not supported
michael@0 8630 this.writeByte(0); // Subaddress : Type
michael@0 8631 this.writeByte(0); // Subaddress : Odd
michael@0 8632 this.writeByte(0); // Subaddress : length
michael@0 8633
michael@0 8634 // User Data
michael@0 8635 let encodeResult = this.encodeUserData(options);
michael@0 8636 this.writeByte(encodeResult.length);
michael@0 8637 for (let i = 0; i < encodeResult.length; i++) {
michael@0 8638 this.writeByte(encodeResult[i]);
michael@0 8639 }
michael@0 8640
michael@0 8641 encodeResult = null;
michael@0 8642 },
michael@0 8643
michael@0 8644 /**
michael@0 8645 * Data writters
michael@0 8646 */
michael@0 8647 writeInt: function(value) {
michael@0 8648 this.context.Buf.writeInt32(value);
michael@0 8649 },
michael@0 8650
michael@0 8651 writeByte: function(value) {
michael@0 8652 this.context.Buf.writeInt32(value & 0xFF);
michael@0 8653 },
michael@0 8654
michael@0 8655 /**
michael@0 8656 * Transform GSM DCS to CDMA encoding.
michael@0 8657 */
michael@0 8658 gsmDcsToCdmaEncoding: function(encoding) {
michael@0 8659 switch (encoding) {
michael@0 8660 case PDU_DCS_MSG_CODING_7BITS_ALPHABET:
michael@0 8661 return PDU_CDMA_MSG_CODING_7BITS_ASCII;
michael@0 8662 case PDU_DCS_MSG_CODING_8BITS_ALPHABET:
michael@0 8663 return PDU_CDMA_MSG_CODING_OCTET;
michael@0 8664 case PDU_DCS_MSG_CODING_16BITS_ALPHABET:
michael@0 8665 return PDU_CDMA_MSG_CODING_UNICODE;
michael@0 8666 }
michael@0 8667 throw new Error("gsmDcsToCdmaEncoding(): Invalid GSM SMS DCS value: " + encoding);
michael@0 8668 },
michael@0 8669
michael@0 8670 /**
michael@0 8671 * Encode address into CDMA address format, as a byte array.
michael@0 8672 *
michael@0 8673 * @see 3GGP2 C.S0015-B 2.0, 3.4.3.3 Address Parameters
michael@0 8674 *
michael@0 8675 * @param address
michael@0 8676 * String of address to be encoded
michael@0 8677 */
michael@0 8678 encodeAddr: function(address) {
michael@0 8679 let result = {};
michael@0 8680
michael@0 8681 result.numberType = PDU_CDMA_MSG_ADDR_NUMBER_TYPE_UNKNOWN;
michael@0 8682 result.numberPlan = PDU_CDMA_MSG_ADDR_NUMBER_TYPE_UNKNOWN;
michael@0 8683
michael@0 8684 if (address[0] === '+') {
michael@0 8685 address = address.substring(1);
michael@0 8686 }
michael@0 8687
michael@0 8688 // Try encode with DTMF first
michael@0 8689 result.digitMode = PDU_CDMA_MSG_ADDR_DIGIT_MODE_DTMF;
michael@0 8690 result.numberMode = PDU_CDMA_MSG_ADDR_NUMBER_MODE_ANSI;
michael@0 8691
michael@0 8692 result.address = [];
michael@0 8693 for (let i = 0; i < address.length; i++) {
michael@0 8694 let addrDigit = this.dtmfChars.indexOf(address.charAt(i));
michael@0 8695 if (addrDigit < 0) {
michael@0 8696 result.digitMode = PDU_CDMA_MSG_ADDR_DIGIT_MODE_ASCII;
michael@0 8697 result.numberMode = PDU_CDMA_MSG_ADDR_NUMBER_MODE_ASCII;
michael@0 8698 result.address = [];
michael@0 8699 break;
michael@0 8700 }
michael@0 8701 result.address.push(addrDigit);
michael@0 8702 }
michael@0 8703
michael@0 8704 // Address can't be encoded with DTMF, then use 7-bit ASCII
michael@0 8705 if (result.digitMode !== PDU_CDMA_MSG_ADDR_DIGIT_MODE_DTMF) {
michael@0 8706 if (address.indexOf("@") !== -1) {
michael@0 8707 result.numberType = PDU_CDMA_MSG_ADDR_NUMBER_TYPE_NATIONAL;
michael@0 8708 }
michael@0 8709
michael@0 8710 for (let i = 0; i < address.length; i++) {
michael@0 8711 result.address.push(address.charCodeAt(i) & 0x7F);
michael@0 8712 }
michael@0 8713 }
michael@0 8714
michael@0 8715 return result;
michael@0 8716 },
michael@0 8717
michael@0 8718 /**
michael@0 8719 * Encode SMS contents in options into CDMA userData field.
michael@0 8720 * Corresponding and required subparameters will be added automatically.
michael@0 8721 *
michael@0 8722 * @see 3GGP2 C.S0015-B 2.0, 3.4.3.7 Bearer Data
michael@0 8723 * 4.5 Bearer Data Parameters
michael@0 8724 *
michael@0 8725 * Current used key in options:
michael@0 8726 * @param body
michael@0 8727 * String containing the message to be sent, segmented part
michael@0 8728 * @param encoding
michael@0 8729 * Encoding method of CDMA, can be transformed from GSM DCS by function
michael@0 8730 * cdmaPduHelp.gsmDcsToCdmaEncoding()
michael@0 8731 * @param encodedBodyLength
michael@0 8732 * Length of the user data when encoded with the given DCS. For UCS2,
michael@0 8733 * in bytes; for 7-bit, in septets.
michael@0 8734 * @param requestStatusReport
michael@0 8735 * Request status report.
michael@0 8736 * @param segmentRef
michael@0 8737 * Reference number of concatenated SMS message
michael@0 8738 * @param segmentMaxSeq
michael@0 8739 * Total number of concatenated SMS message
michael@0 8740 * @param segmentSeq
michael@0 8741 * Sequence number of concatenated SMS message
michael@0 8742 */
michael@0 8743 encodeUserData: function(options) {
michael@0 8744 let userDataBuffer = [];
michael@0 8745 this.context.BitBufferHelper.startWrite(userDataBuffer);
michael@0 8746
michael@0 8747 // Message Identifier
michael@0 8748 this.encodeUserDataMsgId(options);
michael@0 8749
michael@0 8750 // User Data
michael@0 8751 this.encodeUserDataMsg(options);
michael@0 8752
michael@0 8753 // Reply Option
michael@0 8754 this.encodeUserDataReplyOption(options);
michael@0 8755
michael@0 8756 return userDataBuffer;
michael@0 8757 },
michael@0 8758
michael@0 8759 /**
michael@0 8760 * User data subparameter encoder : Message Identifier
michael@0 8761 *
michael@0 8762 * @see 3GGP2 C.S0015-B 2.0, 4.5.1 Message Identifier
michael@0 8763 */
michael@0 8764 encodeUserDataMsgId: function(options) {
michael@0 8765 let BitBufferHelper = this.context.BitBufferHelper;
michael@0 8766 BitBufferHelper.writeBits(PDU_CDMA_MSG_USERDATA_MSG_ID, 8);
michael@0 8767 BitBufferHelper.writeBits(3, 8);
michael@0 8768 BitBufferHelper.writeBits(PDU_CDMA_MSG_TYPE_SUBMIT, 4);
michael@0 8769 BitBufferHelper.writeBits(1, 16); // TODO: How to get message ID?
michael@0 8770 if (options.segmentMaxSeq > 1) {
michael@0 8771 BitBufferHelper.writeBits(1, 1);
michael@0 8772 } else {
michael@0 8773 BitBufferHelper.writeBits(0, 1);
michael@0 8774 }
michael@0 8775
michael@0 8776 BitBufferHelper.flushWithPadding();
michael@0 8777 },
michael@0 8778
michael@0 8779 /**
michael@0 8780 * User data subparameter encoder : User Data
michael@0 8781 *
michael@0 8782 * @see 3GGP2 C.S0015-B 2.0, 4.5.2 User Data
michael@0 8783 */
michael@0 8784 encodeUserDataMsg: function(options) {
michael@0 8785 let BitBufferHelper = this.context.BitBufferHelper;
michael@0 8786 BitBufferHelper.writeBits(PDU_CDMA_MSG_USERDATA_BODY, 8);
michael@0 8787 // Reserve space for length
michael@0 8788 BitBufferHelper.writeBits(0, 8);
michael@0 8789 let lengthPosition = BitBufferHelper.getWriteBufferSize();
michael@0 8790
michael@0 8791 BitBufferHelper.writeBits(options.encoding, 5);
michael@0 8792
michael@0 8793 // Add user data header for message segement
michael@0 8794 let msgBody = options.body,
michael@0 8795 msgBodySize = (options.encoding === PDU_CDMA_MSG_CODING_7BITS_ASCII ?
michael@0 8796 options.encodedBodyLength :
michael@0 8797 msgBody.length);
michael@0 8798 if (options.segmentMaxSeq > 1) {
michael@0 8799 if (options.encoding === PDU_CDMA_MSG_CODING_7BITS_ASCII) {
michael@0 8800 BitBufferHelper.writeBits(msgBodySize + 7, 8); // Required length for user data header, in septet(7-bit)
michael@0 8801
michael@0 8802 BitBufferHelper.writeBits(5, 8); // total header length 5 bytes
michael@0 8803 BitBufferHelper.writeBits(PDU_IEI_CONCATENATED_SHORT_MESSAGES_8BIT, 8); // header id 0
michael@0 8804 BitBufferHelper.writeBits(3, 8); // length of element for id 0 is 3
michael@0 8805 BitBufferHelper.writeBits(options.segmentRef & 0xFF, 8); // Segement reference
michael@0 8806 BitBufferHelper.writeBits(options.segmentMaxSeq & 0xFF, 8); // Max segment
michael@0 8807 BitBufferHelper.writeBits(options.segmentSeq & 0xFF, 8); // Current segment
michael@0 8808 BitBufferHelper.writeBits(0, 1); // Padding to make header data septet(7-bit) aligned
michael@0 8809 } else {
michael@0 8810 if (options.encoding === PDU_CDMA_MSG_CODING_UNICODE) {
michael@0 8811 BitBufferHelper.writeBits(msgBodySize + 3, 8); // Required length for user data header, in 16-bit
michael@0 8812 } else {
michael@0 8813 BitBufferHelper.writeBits(msgBodySize + 6, 8); // Required length for user data header, in octet(8-bit)
michael@0 8814 }
michael@0 8815
michael@0 8816 BitBufferHelper.writeBits(5, 8); // total header length 5 bytes
michael@0 8817 BitBufferHelper.writeBits(PDU_IEI_CONCATENATED_SHORT_MESSAGES_8BIT, 8); // header id 0
michael@0 8818 BitBufferHelper.writeBits(3, 8); // length of element for id 0 is 3
michael@0 8819 BitBufferHelper.writeBits(options.segmentRef & 0xFF, 8); // Segement reference
michael@0 8820 BitBufferHelper.writeBits(options.segmentMaxSeq & 0xFF, 8); // Max segment
michael@0 8821 BitBufferHelper.writeBits(options.segmentSeq & 0xFF, 8); // Current segment
michael@0 8822 }
michael@0 8823 } else {
michael@0 8824 BitBufferHelper.writeBits(msgBodySize, 8);
michael@0 8825 }
michael@0 8826
michael@0 8827 // Encode message based on encoding method
michael@0 8828 const langTable = PDU_NL_LOCKING_SHIFT_TABLES[PDU_NL_IDENTIFIER_DEFAULT];
michael@0 8829 const langShiftTable = PDU_NL_SINGLE_SHIFT_TABLES[PDU_NL_IDENTIFIER_DEFAULT];
michael@0 8830 for (let i = 0; i < msgBody.length; i++) {
michael@0 8831 switch (options.encoding) {
michael@0 8832 case PDU_CDMA_MSG_CODING_OCTET: {
michael@0 8833 let msgDigit = msgBody.charCodeAt(i);
michael@0 8834 BitBufferHelper.writeBits(msgDigit, 8);
michael@0 8835 break;
michael@0 8836 }
michael@0 8837 case PDU_CDMA_MSG_CODING_7BITS_ASCII: {
michael@0 8838 let msgDigit = msgBody.charCodeAt(i),
michael@0 8839 msgDigitChar = msgBody.charAt(i);
michael@0 8840
michael@0 8841 if (msgDigit >= 32) {
michael@0 8842 BitBufferHelper.writeBits(msgDigit, 7);
michael@0 8843 } else {
michael@0 8844 msgDigit = langTable.indexOf(msgDigitChar);
michael@0 8845
michael@0 8846 if (msgDigit === PDU_NL_EXTENDED_ESCAPE) {
michael@0 8847 break;
michael@0 8848 }
michael@0 8849 if (msgDigit >= 0) {
michael@0 8850 BitBufferHelper.writeBits(msgDigit, 7);
michael@0 8851 } else {
michael@0 8852 msgDigit = langShiftTable.indexOf(msgDigitChar);
michael@0 8853 if (msgDigit == -1) {
michael@0 8854 throw new Error("'" + msgDigitChar + "' is not in 7 bit alphabet "
michael@0 8855 + langIndex + ":" + langShiftIndex + "!");
michael@0 8856 }
michael@0 8857
michael@0 8858 if (msgDigit === PDU_NL_RESERVED_CONTROL) {
michael@0 8859 break;
michael@0 8860 }
michael@0 8861
michael@0 8862 BitBufferHelper.writeBits(PDU_NL_EXTENDED_ESCAPE, 7);
michael@0 8863 BitBufferHelper.writeBits(msgDigit, 7);
michael@0 8864 }
michael@0 8865 }
michael@0 8866 break;
michael@0 8867 }
michael@0 8868 case PDU_CDMA_MSG_CODING_UNICODE: {
michael@0 8869 let msgDigit = msgBody.charCodeAt(i);
michael@0 8870 BitBufferHelper.writeBits(msgDigit, 16);
michael@0 8871 break;
michael@0 8872 }
michael@0 8873 }
michael@0 8874 }
michael@0 8875 BitBufferHelper.flushWithPadding();
michael@0 8876
michael@0 8877 // Fill length
michael@0 8878 let currentPosition = BitBufferHelper.getWriteBufferSize();
michael@0 8879 BitBufferHelper.overwriteWriteBuffer(lengthPosition - 1, [currentPosition - lengthPosition]);
michael@0 8880 },
michael@0 8881
michael@0 8882 /**
michael@0 8883 * User data subparameter encoder : Reply Option
michael@0 8884 *
michael@0 8885 * @see 3GGP2 C.S0015-B 2.0, 4.5.11 Reply Option
michael@0 8886 */
michael@0 8887 encodeUserDataReplyOption: function(options) {
michael@0 8888 if (options.requestStatusReport) {
michael@0 8889 let BitBufferHelper = this.context.BitBufferHelper;
michael@0 8890 BitBufferHelper.writeBits(PDU_CDMA_MSG_USERDATA_REPLY_OPTION, 8);
michael@0 8891 BitBufferHelper.writeBits(1, 8);
michael@0 8892 BitBufferHelper.writeBits(0, 1); // USER_ACK_REQ
michael@0 8893 BitBufferHelper.writeBits(1, 1); // DAK_REQ
michael@0 8894 BitBufferHelper.flushWithPadding();
michael@0 8895 }
michael@0 8896 },
michael@0 8897
michael@0 8898 /**
michael@0 8899 * Entry point for SMS decoding, the returned object is made compatible
michael@0 8900 * with existing readMessage() of GsmPDUHelper
michael@0 8901 */
michael@0 8902 readMessage: function() {
michael@0 8903 let message = {};
michael@0 8904
michael@0 8905 // Teleservice Identifier
michael@0 8906 message.teleservice = this.readInt();
michael@0 8907
michael@0 8908 // Message Type
michael@0 8909 let isServicePresent = this.readByte();
michael@0 8910 if (isServicePresent) {
michael@0 8911 message.messageType = PDU_CDMA_MSG_TYPE_BROADCAST;
michael@0 8912 } else {
michael@0 8913 if (message.teleservice) {
michael@0 8914 message.messageType = PDU_CDMA_MSG_TYPE_P2P;
michael@0 8915 } else {
michael@0 8916 message.messageType = PDU_CDMA_MSG_TYPE_ACK;
michael@0 8917 }
michael@0 8918 }
michael@0 8919
michael@0 8920 // Service Category
michael@0 8921 message.service = this.readInt();
michael@0 8922
michael@0 8923 // Originated Address
michael@0 8924 let addrInfo = {};
michael@0 8925 addrInfo.digitMode = (this.readInt() & 0x01);
michael@0 8926 addrInfo.numberMode = (this.readInt() & 0x01);
michael@0 8927 addrInfo.numberType = (this.readInt() & 0x01);
michael@0 8928 addrInfo.numberPlan = (this.readInt() & 0x01);
michael@0 8929 addrInfo.addrLength = this.readByte();
michael@0 8930 addrInfo.address = [];
michael@0 8931 for (let i = 0; i < addrInfo.addrLength; i++) {
michael@0 8932 addrInfo.address.push(this.readByte());
michael@0 8933 }
michael@0 8934 message.sender = this.decodeAddr(addrInfo);
michael@0 8935
michael@0 8936 // Originated Subaddress
michael@0 8937 addrInfo.Type = (this.readInt() & 0x07);
michael@0 8938 addrInfo.Odd = (this.readByte() & 0x01);
michael@0 8939 addrInfo.addrLength = this.readByte();
michael@0 8940 for (let i = 0; i < addrInfo.addrLength; i++) {
michael@0 8941 let addrDigit = this.readByte();
michael@0 8942 message.sender += String.fromCharCode(addrDigit);
michael@0 8943 }
michael@0 8944
michael@0 8945 // Bearer Data
michael@0 8946 this.decodeUserData(message);
michael@0 8947
michael@0 8948 // Bearer Data Sub-Parameter: User Data
michael@0 8949 let userData = message[PDU_CDMA_MSG_USERDATA_BODY];
michael@0 8950 [message.header, message.body, message.encoding, message.data] =
michael@0 8951 (userData) ? [userData.header, userData.body, userData.encoding, userData.data]
michael@0 8952 : [null, null, null, null];
michael@0 8953
michael@0 8954 // Bearer Data Sub-Parameter: Message Status
michael@0 8955 // Success Delivery (0) if both Message Status and User Data are absent.
michael@0 8956 // Message Status absent (-1) if only User Data is available.
michael@0 8957 let msgStatus = message[PDU_CDMA_MSG_USER_DATA_MSG_STATUS];
michael@0 8958 [message.errorClass, message.msgStatus] =
michael@0 8959 (msgStatus) ? [msgStatus.errorClass, msgStatus.msgStatus]
michael@0 8960 : ((message.body) ? [-1, -1] : [0, 0]);
michael@0 8961
michael@0 8962 // Transform message to GSM msg
michael@0 8963 let msg = {
michael@0 8964 SMSC: "",
michael@0 8965 mti: 0,
michael@0 8966 udhi: 0,
michael@0 8967 sender: message.sender,
michael@0 8968 recipient: null,
michael@0 8969 pid: PDU_PID_DEFAULT,
michael@0 8970 epid: PDU_PID_DEFAULT,
michael@0 8971 dcs: 0,
michael@0 8972 mwi: null,
michael@0 8973 replace: false,
michael@0 8974 header: message.header,
michael@0 8975 body: message.body,
michael@0 8976 data: message.data,
michael@0 8977 sentTimestamp: message[PDU_CDMA_MSG_USERDATA_TIMESTAMP],
michael@0 8978 language: message[PDU_CDMA_LANGUAGE_INDICATOR],
michael@0 8979 status: null,
michael@0 8980 scts: null,
michael@0 8981 dt: null,
michael@0 8982 encoding: message.encoding,
michael@0 8983 messageClass: GECKO_SMS_MESSAGE_CLASSES[PDU_DCS_MSG_CLASS_NORMAL],
michael@0 8984 messageType: message.messageType,
michael@0 8985 serviceCategory: message.service,
michael@0 8986 subMsgType: message[PDU_CDMA_MSG_USERDATA_MSG_ID].msgType,
michael@0 8987 msgId: message[PDU_CDMA_MSG_USERDATA_MSG_ID].msgId,
michael@0 8988 errorClass: message.errorClass,
michael@0 8989 msgStatus: message.msgStatus,
michael@0 8990 teleservice: message.teleservice
michael@0 8991 };
michael@0 8992
michael@0 8993 return msg;
michael@0 8994 },
michael@0 8995
michael@0 8996 /**
michael@0 8997 * Helper for processing received SMS parcel data.
michael@0 8998 *
michael@0 8999 * @param length
michael@0 9000 * Length of SMS string in the incoming parcel.
michael@0 9001 *
michael@0 9002 * @return Message parsed or null for invalid message.
michael@0 9003 */
michael@0 9004 processReceivedSms: function(length) {
michael@0 9005 if (!length) {
michael@0 9006 if (DEBUG) this.context.debug("Received empty SMS!");
michael@0 9007 return [null, PDU_FCS_UNSPECIFIED];
michael@0 9008 }
michael@0 9009
michael@0 9010 let message = this.readMessage();
michael@0 9011 if (DEBUG) this.context.debug("Got new SMS: " + JSON.stringify(message));
michael@0 9012
michael@0 9013 // Determine result
michael@0 9014 if (!message) {
michael@0 9015 return [null, PDU_FCS_UNSPECIFIED];
michael@0 9016 }
michael@0 9017
michael@0 9018 return [message, PDU_FCS_OK];
michael@0 9019 },
michael@0 9020
michael@0 9021 /**
michael@0 9022 * Data readers
michael@0 9023 */
michael@0 9024 readInt: function() {
michael@0 9025 return this.context.Buf.readInt32();
michael@0 9026 },
michael@0 9027
michael@0 9028 readByte: function() {
michael@0 9029 return (this.context.Buf.readInt32() & 0xFF);
michael@0 9030 },
michael@0 9031
michael@0 9032 /**
michael@0 9033 * Decode CDMA address data into address string
michael@0 9034 *
michael@0 9035 * @see 3GGP2 C.S0015-B 2.0, 3.4.3.3 Address Parameters
michael@0 9036 *
michael@0 9037 * Required key in addrInfo
michael@0 9038 * @param addrLength
michael@0 9039 * Length of address
michael@0 9040 * @param digitMode
michael@0 9041 * Address encoding method
michael@0 9042 * @param address
michael@0 9043 * Array of encoded address data
michael@0 9044 */
michael@0 9045 decodeAddr: function(addrInfo) {
michael@0 9046 let result = "";
michael@0 9047 for (let i = 0; i < addrInfo.addrLength; i++) {
michael@0 9048 if (addrInfo.digitMode === PDU_CDMA_MSG_ADDR_DIGIT_MODE_DTMF) {
michael@0 9049 result += this.dtmfChars.charAt(addrInfo.address[i]);
michael@0 9050 } else {
michael@0 9051 result += String.fromCharCode(addrInfo.address[i]);
michael@0 9052 }
michael@0 9053 }
michael@0 9054 return result;
michael@0 9055 },
michael@0 9056
michael@0 9057 /**
michael@0 9058 * Read userData in parcel buffer and decode into message object.
michael@0 9059 * Each subparameter will be stored in corresponding key.
michael@0 9060 *
michael@0 9061 * @see 3GGP2 C.S0015-B 2.0, 3.4.3.7 Bearer Data
michael@0 9062 * 4.5 Bearer Data Parameters
michael@0 9063 */
michael@0 9064 decodeUserData: function(message) {
michael@0 9065 let userDataLength = this.readInt();
michael@0 9066
michael@0 9067 while (userDataLength > 0) {
michael@0 9068 let id = this.readByte(),
michael@0 9069 length = this.readByte(),
michael@0 9070 userDataBuffer = [];
michael@0 9071
michael@0 9072 for (let i = 0; i < length; i++) {
michael@0 9073 userDataBuffer.push(this.readByte());
michael@0 9074 }
michael@0 9075
michael@0 9076 this.context.BitBufferHelper.startRead(userDataBuffer);
michael@0 9077
michael@0 9078 switch (id) {
michael@0 9079 case PDU_CDMA_MSG_USERDATA_MSG_ID:
michael@0 9080 message[id] = this.decodeUserDataMsgId();
michael@0 9081 break;
michael@0 9082 case PDU_CDMA_MSG_USERDATA_BODY:
michael@0 9083 message[id] = this.decodeUserDataMsg(message[PDU_CDMA_MSG_USERDATA_MSG_ID].userHeader);
michael@0 9084 break;
michael@0 9085 case PDU_CDMA_MSG_USERDATA_TIMESTAMP:
michael@0 9086 message[id] = this.decodeUserDataTimestamp();
michael@0 9087 break;
michael@0 9088 case PDU_CDMA_MSG_USERDATA_REPLY_OPTION:
michael@0 9089 message[id] = this.decodeUserDataReplyOption();
michael@0 9090 break;
michael@0 9091 case PDU_CDMA_LANGUAGE_INDICATOR:
michael@0 9092 message[id] = this.decodeLanguageIndicator();
michael@0 9093 break;
michael@0 9094 case PDU_CDMA_MSG_USERDATA_CALLBACK_NUMBER:
michael@0 9095 message[id] = this.decodeUserDataCallbackNumber();
michael@0 9096 break;
michael@0 9097 case PDU_CDMA_MSG_USER_DATA_MSG_STATUS:
michael@0 9098 message[id] = this.decodeUserDataMsgStatus();
michael@0 9099 break;
michael@0 9100 }
michael@0 9101
michael@0 9102 userDataLength -= (length + 2);
michael@0 9103 userDataBuffer = [];
michael@0 9104 }
michael@0 9105 },
michael@0 9106
michael@0 9107 /**
michael@0 9108 * User data subparameter decoder: Message Identifier
michael@0 9109 *
michael@0 9110 * @see 3GGP2 C.S0015-B 2.0, 4.5.1 Message Identifier
michael@0 9111 */
michael@0 9112 decodeUserDataMsgId: function() {
michael@0 9113 let result = {};
michael@0 9114 let BitBufferHelper = this.context.BitBufferHelper;
michael@0 9115 result.msgType = BitBufferHelper.readBits(4);
michael@0 9116 result.msgId = BitBufferHelper.readBits(16);
michael@0 9117 result.userHeader = BitBufferHelper.readBits(1);
michael@0 9118
michael@0 9119 return result;
michael@0 9120 },
michael@0 9121
michael@0 9122 /**
michael@0 9123 * Decode user data header, we only care about segment information
michael@0 9124 * on CDMA.
michael@0 9125 *
michael@0 9126 * This function is mostly copied from gsmPduHelper.readUserDataHeader() but
michael@0 9127 * change the read function, because CDMA user header decoding is't byte-wise
michael@0 9128 * aligned.
michael@0 9129 */
michael@0 9130 decodeUserDataHeader: function(encoding) {
michael@0 9131 let BitBufferHelper = this.context.BitBufferHelper;
michael@0 9132 let header = {},
michael@0 9133 headerSize = BitBufferHelper.readBits(8),
michael@0 9134 userDataHeaderSize = headerSize + 1,
michael@0 9135 headerPaddingBits = 0;
michael@0 9136
michael@0 9137 // Calculate header size
michael@0 9138 if (encoding === PDU_DCS_MSG_CODING_7BITS_ALPHABET) {
michael@0 9139 // Length is in 7-bit
michael@0 9140 header.length = Math.ceil(userDataHeaderSize * 8 / 7);
michael@0 9141 // Calulate padding length
michael@0 9142 headerPaddingBits = (header.length * 7) - (userDataHeaderSize * 8);
michael@0 9143 } else if (encoding === PDU_DCS_MSG_CODING_8BITS_ALPHABET) {
michael@0 9144 header.length = userDataHeaderSize;
michael@0 9145 } else {
michael@0 9146 header.length = userDataHeaderSize / 2;
michael@0 9147 }
michael@0 9148
michael@0 9149 while (headerSize) {
michael@0 9150 let identifier = BitBufferHelper.readBits(8),
michael@0 9151 length = BitBufferHelper.readBits(8);
michael@0 9152
michael@0 9153 headerSize -= (2 + length);
michael@0 9154
michael@0 9155 switch (identifier) {
michael@0 9156 case PDU_IEI_CONCATENATED_SHORT_MESSAGES_8BIT: {
michael@0 9157 let ref = BitBufferHelper.readBits(8),
michael@0 9158 max = BitBufferHelper.readBits(8),
michael@0 9159 seq = BitBufferHelper.readBits(8);
michael@0 9160 if (max && seq && (seq <= max)) {
michael@0 9161 header.segmentRef = ref;
michael@0 9162 header.segmentMaxSeq = max;
michael@0 9163 header.segmentSeq = seq;
michael@0 9164 }
michael@0 9165 break;
michael@0 9166 }
michael@0 9167 case PDU_IEI_APPLICATION_PORT_ADDRESSING_SCHEME_8BIT: {
michael@0 9168 let dstp = BitBufferHelper.readBits(8),
michael@0 9169 orip = BitBufferHelper.readBits(8);
michael@0 9170 if ((dstp < PDU_APA_RESERVED_8BIT_PORTS)
michael@0 9171 || (orip < PDU_APA_RESERVED_8BIT_PORTS)) {
michael@0 9172 // 3GPP TS 23.040 clause 9.2.3.24.3: "A receiving entity shall
michael@0 9173 // ignore any information element where the value of the
michael@0 9174 // Information-Element-Data is Reserved or not supported"
michael@0 9175 break;
michael@0 9176 }
michael@0 9177 header.destinationPort = dstp;
michael@0 9178 header.originatorPort = orip;
michael@0 9179 break;
michael@0 9180 }
michael@0 9181 case PDU_IEI_APPLICATION_PORT_ADDRESSING_SCHEME_16BIT: {
michael@0 9182 let dstp = (BitBufferHelper.readBits(8) << 8) | BitBufferHelper.readBits(8),
michael@0 9183 orip = (BitBufferHelper.readBits(8) << 8) | BitBufferHelper.readBits(8);
michael@0 9184 // 3GPP TS 23.040 clause 9.2.3.24.4: "A receiving entity shall
michael@0 9185 // ignore any information element where the value of the
michael@0 9186 // Information-Element-Data is Reserved or not supported"
michael@0 9187 if ((dstp < PDU_APA_VALID_16BIT_PORTS)
michael@0 9188 && (orip < PDU_APA_VALID_16BIT_PORTS)) {
michael@0 9189 header.destinationPort = dstp;
michael@0 9190 header.originatorPort = orip;
michael@0 9191 }
michael@0 9192 break;
michael@0 9193 }
michael@0 9194 case PDU_IEI_CONCATENATED_SHORT_MESSAGES_16BIT: {
michael@0 9195 let ref = (BitBufferHelper.readBits(8) << 8) | BitBufferHelper.readBits(8),
michael@0 9196 max = BitBufferHelper.readBits(8),
michael@0 9197 seq = BitBufferHelper.readBits(8);
michael@0 9198 if (max && seq && (seq <= max)) {
michael@0 9199 header.segmentRef = ref;
michael@0 9200 header.segmentMaxSeq = max;
michael@0 9201 header.segmentSeq = seq;
michael@0 9202 }
michael@0 9203 break;
michael@0 9204 }
michael@0 9205 case PDU_IEI_NATIONAL_LANGUAGE_SINGLE_SHIFT: {
michael@0 9206 let langShiftIndex = BitBufferHelper.readBits(8);
michael@0 9207 if (langShiftIndex < PDU_NL_SINGLE_SHIFT_TABLES.length) {
michael@0 9208 header.langShiftIndex = langShiftIndex;
michael@0 9209 }
michael@0 9210 break;
michael@0 9211 }
michael@0 9212 case PDU_IEI_NATIONAL_LANGUAGE_LOCKING_SHIFT: {
michael@0 9213 let langIndex = BitBufferHelper.readBits(8);
michael@0 9214 if (langIndex < PDU_NL_LOCKING_SHIFT_TABLES.length) {
michael@0 9215 header.langIndex = langIndex;
michael@0 9216 }
michael@0 9217 break;
michael@0 9218 }
michael@0 9219 case PDU_IEI_SPECIAL_SMS_MESSAGE_INDICATION: {
michael@0 9220 let msgInd = BitBufferHelper.readBits(8) & 0xFF,
michael@0 9221 msgCount = BitBufferHelper.readBits(8);
michael@0 9222 /*
michael@0 9223 * TS 23.040 V6.8.1 Sec 9.2.3.24.2
michael@0 9224 * bits 1 0 : basic message indication type
michael@0 9225 * bits 4 3 2 : extended message indication type
michael@0 9226 * bits 6 5 : Profile id
michael@0 9227 * bit 7 : storage type
michael@0 9228 */
michael@0 9229 let storeType = msgInd & PDU_MWI_STORE_TYPE_BIT;
michael@0 9230 header.mwi = {};
michael@0 9231 mwi = header.mwi;
michael@0 9232
michael@0 9233 if (storeType == PDU_MWI_STORE_TYPE_STORE) {
michael@0 9234 // Store message because TP_UDH indicates so, note this may override
michael@0 9235 // the setting in DCS, but that is expected
michael@0 9236 mwi.discard = false;
michael@0 9237 } else if (mwi.discard === undefined) {
michael@0 9238 // storeType == PDU_MWI_STORE_TYPE_DISCARD
michael@0 9239 // only override mwi.discard here if it hasn't already been set
michael@0 9240 mwi.discard = true;
michael@0 9241 }
michael@0 9242
michael@0 9243 mwi.msgCount = msgCount & 0xFF;
michael@0 9244 mwi.active = mwi.msgCount > 0;
michael@0 9245
michael@0 9246 if (DEBUG) {
michael@0 9247 this.context.debug("MWI in TP_UDH received: " + JSON.stringify(mwi));
michael@0 9248 }
michael@0 9249 break;
michael@0 9250 }
michael@0 9251 default:
michael@0 9252 // Drop unsupported id
michael@0 9253 for (let i = 0; i < length; i++) {
michael@0 9254 BitBufferHelper.readBits(8);
michael@0 9255 }
michael@0 9256 }
michael@0 9257 }
michael@0 9258
michael@0 9259 // Consume padding bits
michael@0 9260 if (headerPaddingBits) {
michael@0 9261 BitBufferHelper.readBits(headerPaddingBits);
michael@0 9262 }
michael@0 9263
michael@0 9264 return header;
michael@0 9265 },
michael@0 9266
michael@0 9267 getCdmaMsgEncoding: function(encoding) {
michael@0 9268 // Determine encoding method
michael@0 9269 switch (encoding) {
michael@0 9270 case PDU_CDMA_MSG_CODING_7BITS_ASCII:
michael@0 9271 case PDU_CDMA_MSG_CODING_IA5:
michael@0 9272 case PDU_CDMA_MSG_CODING_7BITS_GSM:
michael@0 9273 return PDU_DCS_MSG_CODING_7BITS_ALPHABET;
michael@0 9274 case PDU_CDMA_MSG_CODING_OCTET:
michael@0 9275 case PDU_CDMA_MSG_CODING_IS_91:
michael@0 9276 case PDU_CDMA_MSG_CODING_LATIN_HEBREW:
michael@0 9277 case PDU_CDMA_MSG_CODING_LATIN:
michael@0 9278 return PDU_DCS_MSG_CODING_8BITS_ALPHABET;
michael@0 9279 case PDU_CDMA_MSG_CODING_UNICODE:
michael@0 9280 case PDU_CDMA_MSG_CODING_SHIFT_JIS:
michael@0 9281 case PDU_CDMA_MSG_CODING_KOREAN:
michael@0 9282 return PDU_DCS_MSG_CODING_16BITS_ALPHABET;
michael@0 9283 }
michael@0 9284 return null;
michael@0 9285 },
michael@0 9286
michael@0 9287 decodeCdmaPDUMsg: function(encoding, msgType, msgBodySize) {
michael@0 9288 const langTable = PDU_NL_LOCKING_SHIFT_TABLES[PDU_NL_IDENTIFIER_DEFAULT];
michael@0 9289 const langShiftTable = PDU_NL_SINGLE_SHIFT_TABLES[PDU_NL_IDENTIFIER_DEFAULT];
michael@0 9290 let BitBufferHelper = this.context.BitBufferHelper;
michael@0 9291 let result = "";
michael@0 9292 let msgDigit;
michael@0 9293 switch (encoding) {
michael@0 9294 case PDU_CDMA_MSG_CODING_OCTET: // TODO : Require Test
michael@0 9295 while(msgBodySize > 0) {
michael@0 9296 msgDigit = String.fromCharCode(BitBufferHelper.readBits(8));
michael@0 9297 result += msgDigit;
michael@0 9298 msgBodySize--;
michael@0 9299 }
michael@0 9300 break;
michael@0 9301 case PDU_CDMA_MSG_CODING_IS_91: // TODO : Require Test
michael@0 9302 // Referenced from android code
michael@0 9303 switch (msgType) {
michael@0 9304 case PDU_CDMA_MSG_CODING_IS_91_TYPE_SMS:
michael@0 9305 case PDU_CDMA_MSG_CODING_IS_91_TYPE_SMS_FULL:
michael@0 9306 case PDU_CDMA_MSG_CODING_IS_91_TYPE_VOICEMAIL_STATUS:
michael@0 9307 while(msgBodySize > 0) {
michael@0 9308 msgDigit = String.fromCharCode(BitBufferHelper.readBits(6) + 0x20);
michael@0 9309 result += msgDigit;
michael@0 9310 msgBodySize--;
michael@0 9311 }
michael@0 9312 break;
michael@0 9313 case PDU_CDMA_MSG_CODING_IS_91_TYPE_CLI:
michael@0 9314 let addrInfo = {};
michael@0 9315 addrInfo.digitMode = PDU_CDMA_MSG_ADDR_DIGIT_MODE_DTMF;
michael@0 9316 addrInfo.numberMode = PDU_CDMA_MSG_ADDR_NUMBER_MODE_ANSI;
michael@0 9317 addrInfo.numberType = PDU_CDMA_MSG_ADDR_NUMBER_TYPE_UNKNOWN;
michael@0 9318 addrInfo.numberPlan = PDU_CDMA_MSG_ADDR_NUMBER_PLAN_UNKNOWN;
michael@0 9319 addrInfo.addrLength = msgBodySize;
michael@0 9320 addrInfo.address = [];
michael@0 9321 for (let i = 0; i < addrInfo.addrLength; i++) {
michael@0 9322 addrInfo.address.push(BitBufferHelper.readBits(4));
michael@0 9323 }
michael@0 9324 result = this.decodeAddr(addrInfo);
michael@0 9325 break;
michael@0 9326 }
michael@0 9327 // Fall through.
michael@0 9328 case PDU_CDMA_MSG_CODING_7BITS_ASCII:
michael@0 9329 case PDU_CDMA_MSG_CODING_IA5: // TODO : Require Test
michael@0 9330 while(msgBodySize > 0) {
michael@0 9331 msgDigit = BitBufferHelper.readBits(7);
michael@0 9332 if (msgDigit >= 32) {
michael@0 9333 msgDigit = String.fromCharCode(msgDigit);
michael@0 9334 } else {
michael@0 9335 if (msgDigit !== PDU_NL_EXTENDED_ESCAPE) {
michael@0 9336 msgDigit = langTable[msgDigit];
michael@0 9337 } else {
michael@0 9338 msgDigit = BitBufferHelper.readBits(7);
michael@0 9339 msgBodySize--;
michael@0 9340 msgDigit = langShiftTable[msgDigit];
michael@0 9341 }
michael@0 9342 }
michael@0 9343 result += msgDigit;
michael@0 9344 msgBodySize--;
michael@0 9345 }
michael@0 9346 break;
michael@0 9347 case PDU_CDMA_MSG_CODING_UNICODE:
michael@0 9348 while(msgBodySize > 0) {
michael@0 9349 msgDigit = String.fromCharCode(BitBufferHelper.readBits(16));
michael@0 9350 result += msgDigit;
michael@0 9351 msgBodySize--;
michael@0 9352 }
michael@0 9353 break;
michael@0 9354 case PDU_CDMA_MSG_CODING_7BITS_GSM: // TODO : Require Test
michael@0 9355 while(msgBodySize > 0) {
michael@0 9356 msgDigit = BitBufferHelper.readBits(7);
michael@0 9357 if (msgDigit !== PDU_NL_EXTENDED_ESCAPE) {
michael@0 9358 msgDigit = langTable[msgDigit];
michael@0 9359 } else {
michael@0 9360 msgDigit = BitBufferHelper.readBits(7);
michael@0 9361 msgBodySize--;
michael@0 9362 msgDigit = langShiftTable[msgDigit];
michael@0 9363 }
michael@0 9364 result += msgDigit;
michael@0 9365 msgBodySize--;
michael@0 9366 }
michael@0 9367 break;
michael@0 9368 case PDU_CDMA_MSG_CODING_LATIN: // TODO : Require Test
michael@0 9369 // Reference : http://en.wikipedia.org/wiki/ISO/IEC_8859-1
michael@0 9370 while(msgBodySize > 0) {
michael@0 9371 msgDigit = String.fromCharCode(BitBufferHelper.readBits(8));
michael@0 9372 result += msgDigit;
michael@0 9373 msgBodySize--;
michael@0 9374 }
michael@0 9375 break;
michael@0 9376 case PDU_CDMA_MSG_CODING_LATIN_HEBREW: // TODO : Require Test
michael@0 9377 // Reference : http://en.wikipedia.org/wiki/ISO/IEC_8859-8
michael@0 9378 while(msgBodySize > 0) {
michael@0 9379 msgDigit = BitBufferHelper.readBits(8);
michael@0 9380 if (msgDigit === 0xDF) {
michael@0 9381 msgDigit = String.fromCharCode(0x2017);
michael@0 9382 } else if (msgDigit === 0xFD) {
michael@0 9383 msgDigit = String.fromCharCode(0x200E);
michael@0 9384 } else if (msgDigit === 0xFE) {
michael@0 9385 msgDigit = String.fromCharCode(0x200F);
michael@0 9386 } else if (msgDigit >= 0xE0 && msgDigit <= 0xFA) {
michael@0 9387 msgDigit = String.fromCharCode(0x4F0 + msgDigit);
michael@0 9388 } else {
michael@0 9389 msgDigit = String.fromCharCode(msgDigit);
michael@0 9390 }
michael@0 9391 result += msgDigit;
michael@0 9392 msgBodySize--;
michael@0 9393 }
michael@0 9394 break;
michael@0 9395 case PDU_CDMA_MSG_CODING_SHIFT_JIS:
michael@0 9396 // Reference : http://msdn.microsoft.com/en-US/goglobal/cc305152.aspx
michael@0 9397 // http://demo.icu-project.org/icu-bin/convexp?conv=Shift_JIS
michael@0 9398 let shift_jis_message = [];
michael@0 9399
michael@0 9400 while (msgBodySize > 0) {
michael@0 9401 shift_jis_message.push(BitBufferHelper.readBits(8));
michael@0 9402 msgBodySize--;
michael@0 9403 }
michael@0 9404
michael@0 9405 let decoder = new TextDecoder("shift_jis");
michael@0 9406 result = decoder.decode(new Uint8Array(shift_jis_message));
michael@0 9407 break;
michael@0 9408 case PDU_CDMA_MSG_CODING_KOREAN:
michael@0 9409 case PDU_CDMA_MSG_CODING_GSM_DCS:
michael@0 9410 // Fall through.
michael@0 9411 default:
michael@0 9412 break;
michael@0 9413 }
michael@0 9414 return result;
michael@0 9415 },
michael@0 9416
michael@0 9417 /**
michael@0 9418 * User data subparameter decoder : User Data
michael@0 9419 *
michael@0 9420 * @see 3GGP2 C.S0015-B 2.0, 4.5.2 User Data
michael@0 9421 */
michael@0 9422 decodeUserDataMsg: function(hasUserHeader) {
michael@0 9423 let BitBufferHelper = this.context.BitBufferHelper;
michael@0 9424 let result = {},
michael@0 9425 encoding = BitBufferHelper.readBits(5),
michael@0 9426 msgType;
michael@0 9427
michael@0 9428 if (encoding === PDU_CDMA_MSG_CODING_IS_91) {
michael@0 9429 msgType = BitBufferHelper.readBits(8);
michael@0 9430 }
michael@0 9431 result.encoding = this.getCdmaMsgEncoding(encoding);
michael@0 9432
michael@0 9433 let msgBodySize = BitBufferHelper.readBits(8);
michael@0 9434
michael@0 9435 // For segmented SMS, a user header is included before sms content
michael@0 9436 if (hasUserHeader) {
michael@0 9437 result.header = this.decodeUserDataHeader(result.encoding);
michael@0 9438 // header size is included in body size, they are decoded
michael@0 9439 msgBodySize -= result.header.length;
michael@0 9440 }
michael@0 9441
michael@0 9442 // Store original payload if enconding is OCTET for further handling of WAP Push, etc.
michael@0 9443 if (encoding === PDU_CDMA_MSG_CODING_OCTET && msgBodySize > 0) {
michael@0 9444 result.data = new Uint8Array(msgBodySize);
michael@0 9445 for (let i = 0; i < msgBodySize; i++) {
michael@0 9446 result.data[i] = BitBufferHelper.readBits(8);
michael@0 9447 }
michael@0 9448 BitBufferHelper.backwardReadPilot(8 * msgBodySize);
michael@0 9449 }
michael@0 9450
michael@0 9451 // Decode sms content
michael@0 9452 result.body = this.decodeCdmaPDUMsg(encoding, msgType, msgBodySize);
michael@0 9453
michael@0 9454 return result;
michael@0 9455 },
michael@0 9456
michael@0 9457 decodeBcd: function(value) {
michael@0 9458 return ((value >> 4) & 0xF) * 10 + (value & 0x0F);
michael@0 9459 },
michael@0 9460
michael@0 9461 /**
michael@0 9462 * User data subparameter decoder : Time Stamp
michael@0 9463 *
michael@0 9464 * @see 3GGP2 C.S0015-B 2.0, 4.5.4 Message Center Time Stamp
michael@0 9465 */
michael@0 9466 decodeUserDataTimestamp: function() {
michael@0 9467 let BitBufferHelper = this.context.BitBufferHelper;
michael@0 9468 let year = this.decodeBcd(BitBufferHelper.readBits(8)),
michael@0 9469 month = this.decodeBcd(BitBufferHelper.readBits(8)) - 1,
michael@0 9470 date = this.decodeBcd(BitBufferHelper.readBits(8)),
michael@0 9471 hour = this.decodeBcd(BitBufferHelper.readBits(8)),
michael@0 9472 min = this.decodeBcd(BitBufferHelper.readBits(8)),
michael@0 9473 sec = this.decodeBcd(BitBufferHelper.readBits(8));
michael@0 9474
michael@0 9475 if (year >= 96 && year <= 99) {
michael@0 9476 year += 1900;
michael@0 9477 } else {
michael@0 9478 year += 2000;
michael@0 9479 }
michael@0 9480
michael@0 9481 let result = (new Date(year, month, date, hour, min, sec, 0)).valueOf();
michael@0 9482
michael@0 9483 return result;
michael@0 9484 },
michael@0 9485
michael@0 9486 /**
michael@0 9487 * User data subparameter decoder : Reply Option
michael@0 9488 *
michael@0 9489 * @see 3GGP2 C.S0015-B 2.0, 4.5.11 Reply Option
michael@0 9490 */
michael@0 9491 decodeUserDataReplyOption: function() {
michael@0 9492 let replyAction = this.context.BitBufferHelper.readBits(4),
michael@0 9493 result = { userAck: (replyAction & 0x8) ? true : false,
michael@0 9494 deliverAck: (replyAction & 0x4) ? true : false,
michael@0 9495 readAck: (replyAction & 0x2) ? true : false,
michael@0 9496 report: (replyAction & 0x1) ? true : false
michael@0 9497 };
michael@0 9498
michael@0 9499 return result;
michael@0 9500 },
michael@0 9501
michael@0 9502 /**
michael@0 9503 * User data subparameter decoder : Language Indicator
michael@0 9504 *
michael@0 9505 * @see 3GGP2 C.S0015-B 2.0, 4.5.14 Language Indicator
michael@0 9506 */
michael@0 9507 decodeLanguageIndicator: function() {
michael@0 9508 let language = this.context.BitBufferHelper.readBits(8);
michael@0 9509 let result = CB_CDMA_LANG_GROUP[language];
michael@0 9510 return result;
michael@0 9511 },
michael@0 9512
michael@0 9513 /**
michael@0 9514 * User data subparameter decoder : Call-Back Number
michael@0 9515 *
michael@0 9516 * @see 3GGP2 C.S0015-B 2.0, 4.5.15 Call-Back Number
michael@0 9517 */
michael@0 9518 decodeUserDataCallbackNumber: function() {
michael@0 9519 let BitBufferHelper = this.context.BitBufferHelper;
michael@0 9520 let digitMode = BitBufferHelper.readBits(1);
michael@0 9521 if (digitMode) {
michael@0 9522 let numberType = BitBufferHelper.readBits(3),
michael@0 9523 numberPlan = BitBufferHelper.readBits(4);
michael@0 9524 }
michael@0 9525 let numberFields = BitBufferHelper.readBits(8),
michael@0 9526 result = "";
michael@0 9527 for (let i = 0; i < numberFields; i++) {
michael@0 9528 if (digitMode === PDU_CDMA_MSG_ADDR_DIGIT_MODE_DTMF) {
michael@0 9529 let addrDigit = BitBufferHelper.readBits(4);
michael@0 9530 result += this.dtmfChars.charAt(addrDigit);
michael@0 9531 } else {
michael@0 9532 let addrDigit = BitBufferHelper.readBits(8);
michael@0 9533 result += String.fromCharCode(addrDigit);
michael@0 9534 }
michael@0 9535 }
michael@0 9536
michael@0 9537 return result;
michael@0 9538 },
michael@0 9539
michael@0 9540 /**
michael@0 9541 * User data subparameter decoder : Message Status
michael@0 9542 *
michael@0 9543 * @see 3GGP2 C.S0015-B 2.0, 4.5.21 Message Status
michael@0 9544 */
michael@0 9545 decodeUserDataMsgStatus: function() {
michael@0 9546 let BitBufferHelper = this.context.BitBufferHelper;
michael@0 9547 let result = {
michael@0 9548 errorClass: BitBufferHelper.readBits(2),
michael@0 9549 msgStatus: BitBufferHelper.readBits(6)
michael@0 9550 };
michael@0 9551
michael@0 9552 return result;
michael@0 9553 },
michael@0 9554
michael@0 9555 /**
michael@0 9556 * Decode information record parcel.
michael@0 9557 */
michael@0 9558 decodeInformationRecord: function() {
michael@0 9559 let Buf = this.context.Buf;
michael@0 9560 let record = {};
michael@0 9561 let numOfRecords = Buf.readInt32();
michael@0 9562
michael@0 9563 let type;
michael@0 9564 for (let i = 0; i < numOfRecords; i++) {
michael@0 9565 type = Buf.readInt32();
michael@0 9566
michael@0 9567 switch (type) {
michael@0 9568 /*
michael@0 9569 * Every type is encaped by ril, except extended display
michael@0 9570 */
michael@0 9571 case PDU_CDMA_INFO_REC_TYPE_DISPLAY:
michael@0 9572 record.display = Buf.readString();
michael@0 9573 break;
michael@0 9574 case PDU_CDMA_INFO_REC_TYPE_CALLED_PARTY_NUMBER:
michael@0 9575 record.calledNumber = {};
michael@0 9576 record.calledNumber.number = Buf.readString();
michael@0 9577 record.calledNumber.type = Buf.readInt32();
michael@0 9578 record.calledNumber.plan = Buf.readInt32();
michael@0 9579 record.calledNumber.pi = Buf.readInt32();
michael@0 9580 record.calledNumber.si = Buf.readInt32();
michael@0 9581 break;
michael@0 9582 case PDU_CDMA_INFO_REC_TYPE_CALLING_PARTY_NUMBER:
michael@0 9583 record.callingNumber = {};
michael@0 9584 record.callingNumber.number = Buf.readString();
michael@0 9585 record.callingNumber.type = Buf.readInt32();
michael@0 9586 record.callingNumber.plan = Buf.readInt32();
michael@0 9587 record.callingNumber.pi = Buf.readInt32();
michael@0 9588 record.callingNumber.si = Buf.readInt32();
michael@0 9589 break;
michael@0 9590 case PDU_CDMA_INFO_REC_TYPE_CONNECTED_NUMBER:
michael@0 9591 record.connectedNumber = {};
michael@0 9592 record.connectedNumber.number = Buf.readString();
michael@0 9593 record.connectedNumber.type = Buf.readInt32();
michael@0 9594 record.connectedNumber.plan = Buf.readInt32();
michael@0 9595 record.connectedNumber.pi = Buf.readInt32();
michael@0 9596 record.connectedNumber.si = Buf.readInt32();
michael@0 9597 break;
michael@0 9598 case PDU_CDMA_INFO_REC_TYPE_SIGNAL:
michael@0 9599 record.signal = {};
michael@0 9600 record.signal.present = Buf.readInt32();
michael@0 9601 record.signal.type = Buf.readInt32();
michael@0 9602 record.signal.alertPitch = Buf.readInt32();
michael@0 9603 record.signal.signal = Buf.readInt32();
michael@0 9604 break;
michael@0 9605 case PDU_CDMA_INFO_REC_TYPE_REDIRECTING_NUMBER:
michael@0 9606 record.redirect = {};
michael@0 9607 record.redirect.number = Buf.readString();
michael@0 9608 record.redirect.type = Buf.readInt32();
michael@0 9609 record.redirect.plan = Buf.readInt32();
michael@0 9610 record.redirect.pi = Buf.readInt32();
michael@0 9611 record.redirect.si = Buf.readInt32();
michael@0 9612 record.redirect.reason = Buf.readInt32();
michael@0 9613 break;
michael@0 9614 case PDU_CDMA_INFO_REC_TYPE_LINE_CONTROL:
michael@0 9615 record.lineControl = {};
michael@0 9616 record.lineControl.polarityIncluded = Buf.readInt32();
michael@0 9617 record.lineControl.toggle = Buf.readInt32();
michael@0 9618 record.lineControl.recerse = Buf.readInt32();
michael@0 9619 record.lineControl.powerDenial = Buf.readInt32();
michael@0 9620 break;
michael@0 9621 case PDU_CDMA_INFO_REC_TYPE_EXTENDED_DISPLAY:
michael@0 9622 let length = Buf.readInt32();
michael@0 9623 /*
michael@0 9624 * Extended display is still in format defined in
michael@0 9625 * C.S0005-F v1.0, 3.7.5.16
michael@0 9626 */
michael@0 9627 record.extendedDisplay = {};
michael@0 9628
michael@0 9629 let headerByte = Buf.readInt32();
michael@0 9630 length--;
michael@0 9631 // Based on spec, headerByte must be 0x80 now
michael@0 9632 record.extendedDisplay.indicator = (headerByte >> 7);
michael@0 9633 record.extendedDisplay.type = (headerByte & 0x7F);
michael@0 9634 record.extendedDisplay.records = [];
michael@0 9635
michael@0 9636 while (length > 0) {
michael@0 9637 let display = {};
michael@0 9638
michael@0 9639 display.tag = Buf.readInt32();
michael@0 9640 length--;
michael@0 9641 if (display.tag !== INFO_REC_EXTENDED_DISPLAY_BLANK &&
michael@0 9642 display.tag !== INFO_REC_EXTENDED_DISPLAY_SKIP) {
michael@0 9643 display.content = Buf.readString();
michael@0 9644 length -= (display.content.length + 1);
michael@0 9645 }
michael@0 9646
michael@0 9647 record.extendedDisplay.records.push(display);
michael@0 9648 }
michael@0 9649 break;
michael@0 9650 case PDU_CDMA_INFO_REC_TYPE_T53_CLIR:
michael@0 9651 record.cause = Buf.readInt32();
michael@0 9652 break;
michael@0 9653 case PDU_CDMA_INFO_REC_TYPE_T53_AUDIO_CONTROL:
michael@0 9654 record.audioControl = {};
michael@0 9655 record.audioControl.upLink = Buf.readInt32();
michael@0 9656 record.audioControl.downLink = Buf.readInt32();
michael@0 9657 break;
michael@0 9658 case PDU_CDMA_INFO_REC_TYPE_T53_RELEASE:
michael@0 9659 // Fall through
michael@0 9660 default:
michael@0 9661 throw new Error("UNSOLICITED_CDMA_INFO_REC(), Unsupported information record type " + record.type + "\n");
michael@0 9662 }
michael@0 9663 }
michael@0 9664
michael@0 9665 return record;
michael@0 9666 }
michael@0 9667 };
michael@0 9668
michael@0 9669 /**
michael@0 9670 * Helper for processing ICC PDUs.
michael@0 9671 */
michael@0 9672 function ICCPDUHelperObject(aContext) {
michael@0 9673 this.context = aContext;
michael@0 9674 }
michael@0 9675 ICCPDUHelperObject.prototype = {
michael@0 9676 context: null,
michael@0 9677
michael@0 9678 /**
michael@0 9679 * Read GSM 8-bit unpacked octets,
michael@0 9680 * which are default 7-bit alphabets with bit 8 set to 0.
michael@0 9681 *
michael@0 9682 * @param numOctets
michael@0 9683 * Number of octets to be read.
michael@0 9684 */
michael@0 9685 read8BitUnpackedToString: function(numOctets) {
michael@0 9686 let GsmPDUHelper = this.context.GsmPDUHelper;
michael@0 9687
michael@0 9688 let ret = "";
michael@0 9689 let escapeFound = false;
michael@0 9690 let i;
michael@0 9691 const langTable = PDU_NL_LOCKING_SHIFT_TABLES[PDU_NL_IDENTIFIER_DEFAULT];
michael@0 9692 const langShiftTable = PDU_NL_SINGLE_SHIFT_TABLES[PDU_NL_IDENTIFIER_DEFAULT];
michael@0 9693
michael@0 9694 for(i = 0; i < numOctets; i++) {
michael@0 9695 let octet = GsmPDUHelper.readHexOctet();
michael@0 9696 if (octet == 0xff) {
michael@0 9697 i++;
michael@0 9698 break;
michael@0 9699 }
michael@0 9700
michael@0 9701 if (escapeFound) {
michael@0 9702 escapeFound = false;
michael@0 9703 if (octet == PDU_NL_EXTENDED_ESCAPE) {
michael@0 9704 // According to 3GPP TS 23.038, section 6.2.1.1, NOTE 1, "On
michael@0 9705 // receipt of this code, a receiving entity shall display a space
michael@0 9706 // until another extensiion table is defined."
michael@0 9707 ret += " ";
michael@0 9708 } else if (octet == PDU_NL_RESERVED_CONTROL) {
michael@0 9709 // According to 3GPP TS 23.038 B.2, "This code represents a control
michael@0 9710 // character and therefore must not be used for language specific
michael@0 9711 // characters."
michael@0 9712 ret += " ";
michael@0 9713 } else {
michael@0 9714 ret += langShiftTable[octet];
michael@0 9715 }
michael@0 9716 } else if (octet == PDU_NL_EXTENDED_ESCAPE) {
michael@0 9717 escapeFound = true;
michael@0 9718 } else {
michael@0 9719 ret += langTable[octet];
michael@0 9720 }
michael@0 9721 }
michael@0 9722
michael@0 9723 let Buf = this.context.Buf;
michael@0 9724 Buf.seekIncoming((numOctets - i) * Buf.PDU_HEX_OCTET_SIZE);
michael@0 9725 return ret;
michael@0 9726 },
michael@0 9727
michael@0 9728 /**
michael@0 9729 * Write GSM 8-bit unpacked octets.
michael@0 9730 *
michael@0 9731 * @param numOctets Number of total octets to be writen, including trailing
michael@0 9732 * 0xff.
michael@0 9733 * @param str String to be written. Could be null.
michael@0 9734 */
michael@0 9735 writeStringTo8BitUnpacked: function(numOctets, str) {
michael@0 9736 const langTable = PDU_NL_LOCKING_SHIFT_TABLES[PDU_NL_IDENTIFIER_DEFAULT];
michael@0 9737 const langShiftTable = PDU_NL_SINGLE_SHIFT_TABLES[PDU_NL_IDENTIFIER_DEFAULT];
michael@0 9738
michael@0 9739 let GsmPDUHelper = this.context.GsmPDUHelper;
michael@0 9740
michael@0 9741 // If the character is GSM extended alphabet, two octets will be written.
michael@0 9742 // So we need to keep track of number of octets to be written.
michael@0 9743 let i, j;
michael@0 9744 let len = str ? str.length : 0;
michael@0 9745 for (i = 0, j = 0; i < len && j < numOctets; i++) {
michael@0 9746 let c = str.charAt(i);
michael@0 9747 let octet = langTable.indexOf(c);
michael@0 9748
michael@0 9749 if (octet == -1) {
michael@0 9750 // Make sure we still have enough space to write two octets.
michael@0 9751 if (j + 2 > numOctets) {
michael@0 9752 break;
michael@0 9753 }
michael@0 9754
michael@0 9755 octet = langShiftTable.indexOf(c);
michael@0 9756 if (octet == -1) {
michael@0 9757 // Fallback to ASCII space.
michael@0 9758 octet = langTable.indexOf(' ');
michael@0 9759 }
michael@0 9760 GsmPDUHelper.writeHexOctet(PDU_NL_EXTENDED_ESCAPE);
michael@0 9761 j++;
michael@0 9762 }
michael@0 9763 GsmPDUHelper.writeHexOctet(octet);
michael@0 9764 j++;
michael@0 9765 }
michael@0 9766
michael@0 9767 // trailing 0xff
michael@0 9768 while (j++ < numOctets) {
michael@0 9769 GsmPDUHelper.writeHexOctet(0xff);
michael@0 9770 }
michael@0 9771 },
michael@0 9772
michael@0 9773 /**
michael@0 9774 * Read UCS2 String on UICC.
michael@0 9775 *
michael@0 9776 * @see TS 101.221, Annex A.
michael@0 9777 * @param scheme
michael@0 9778 * Coding scheme for UCS2 on UICC. One of 0x80, 0x81 or 0x82.
michael@0 9779 * @param numOctets
michael@0 9780 * Number of octets to be read as UCS2 string.
michael@0 9781 */
michael@0 9782 readICCUCS2String: function(scheme, numOctets) {
michael@0 9783 let Buf = this.context.Buf;
michael@0 9784 let GsmPDUHelper = this.context.GsmPDUHelper;
michael@0 9785
michael@0 9786 let str = "";
michael@0 9787 switch (scheme) {
michael@0 9788 /**
michael@0 9789 * +------+---------+---------+---------+---------+------+------+
michael@0 9790 * | 0x80 | Ch1_msb | Ch1_lsb | Ch2_msb | Ch2_lsb | 0xff | 0xff |
michael@0 9791 * +------+---------+---------+---------+---------+------+------+
michael@0 9792 */
michael@0 9793 case 0x80:
michael@0 9794 let isOdd = numOctets % 2;
michael@0 9795 let i;
michael@0 9796 for (i = 0; i < numOctets - isOdd; i += 2) {
michael@0 9797 let code = (GsmPDUHelper.readHexOctet() << 8) | GsmPDUHelper.readHexOctet();
michael@0 9798 if (code == 0xffff) {
michael@0 9799 i += 2;
michael@0 9800 break;
michael@0 9801 }
michael@0 9802 str += String.fromCharCode(code);
michael@0 9803 }
michael@0 9804
michael@0 9805 // Skip trailing 0xff
michael@0 9806 Buf.seekIncoming((numOctets - i) * Buf.PDU_HEX_OCTET_SIZE);
michael@0 9807 break;
michael@0 9808 case 0x81: // Fall through
michael@0 9809 case 0x82:
michael@0 9810 /**
michael@0 9811 * +------+-----+--------+-----+-----+-----+--------+------+
michael@0 9812 * | 0x81 | len | offset | Ch1 | Ch2 | ... | Ch_len | 0xff |
michael@0 9813 * +------+-----+--------+-----+-----+-----+--------+------+
michael@0 9814 *
michael@0 9815 * len : The length of characters.
michael@0 9816 * offset : 0hhh hhhh h000 0000
michael@0 9817 * Ch_n: bit 8 = 0
michael@0 9818 * GSM default alphabets
michael@0 9819 * bit 8 = 1
michael@0 9820 * UCS2 character whose char code is (Ch_n & 0x7f) + offset
michael@0 9821 *
michael@0 9822 * +------+-----+------------+------------+-----+-----+-----+--------+
michael@0 9823 * | 0x82 | len | offset_msb | offset_lsb | Ch1 | Ch2 | ... | Ch_len |
michael@0 9824 * +------+-----+------------+------------+-----+-----+-----+--------+
michael@0 9825 *
michael@0 9826 * len : The length of characters.
michael@0 9827 * offset_msb, offset_lsn: offset
michael@0 9828 * Ch_n: bit 8 = 0
michael@0 9829 * GSM default alphabets
michael@0 9830 * bit 8 = 1
michael@0 9831 * UCS2 character whose char code is (Ch_n & 0x7f) + offset
michael@0 9832 */
michael@0 9833 let len = GsmPDUHelper.readHexOctet();
michael@0 9834 let offset, headerLen;
michael@0 9835 if (scheme == 0x81) {
michael@0 9836 offset = GsmPDUHelper.readHexOctet() << 7;
michael@0 9837 headerLen = 2;
michael@0 9838 } else {
michael@0 9839 offset = (GsmPDUHelper.readHexOctet() << 8) | GsmPDUHelper.readHexOctet();
michael@0 9840 headerLen = 3;
michael@0 9841 }
michael@0 9842
michael@0 9843 for (let i = 0; i < len; i++) {
michael@0 9844 let ch = GsmPDUHelper.readHexOctet();
michael@0 9845 if (ch & 0x80) {
michael@0 9846 // UCS2
michael@0 9847 str += String.fromCharCode((ch & 0x7f) + offset);
michael@0 9848 } else {
michael@0 9849 // GSM 8bit
michael@0 9850 let count = 0, gotUCS2 = 0;
michael@0 9851 while ((i + count + 1 < len)) {
michael@0 9852 count++;
michael@0 9853 if (GsmPDUHelper.readHexOctet() & 0x80) {
michael@0 9854 gotUCS2 = 1;
michael@0 9855 break;
michael@0 9856 }
michael@0 9857 }
michael@0 9858 // Unread.
michael@0 9859 // +1 for the GSM alphabet indexed at i,
michael@0 9860 Buf.seekIncoming(-1 * (count + 1) * Buf.PDU_HEX_OCTET_SIZE);
michael@0 9861 str += this.read8BitUnpackedToString(count + 1 - gotUCS2);
michael@0 9862 i += count - gotUCS2;
michael@0 9863 }
michael@0 9864 }
michael@0 9865
michael@0 9866 // Skipping trailing 0xff
michael@0 9867 Buf.seekIncoming((numOctets - len - headerLen) * Buf.PDU_HEX_OCTET_SIZE);
michael@0 9868 break;
michael@0 9869 }
michael@0 9870 return str;
michael@0 9871 },
michael@0 9872
michael@0 9873 /**
michael@0 9874 * Read Alpha Id and Dialling number from TS TS 151.011 clause 10.5.1
michael@0 9875 *
michael@0 9876 * @param recordSize The size of linear fixed record.
michael@0 9877 */
michael@0 9878 readAlphaIdDiallingNumber: function(recordSize) {
michael@0 9879 let Buf = this.context.Buf;
michael@0 9880 let length = Buf.readInt32();
michael@0 9881
michael@0 9882 let alphaLen = recordSize - ADN_FOOTER_SIZE_BYTES;
michael@0 9883 let alphaId = this.readAlphaIdentifier(alphaLen);
michael@0 9884
michael@0 9885 let number = this.readNumberWithLength();
michael@0 9886
michael@0 9887 // Skip 2 unused octets, CCP and EXT1.
michael@0 9888 Buf.seekIncoming(2 * Buf.PDU_HEX_OCTET_SIZE);
michael@0 9889 Buf.readStringDelimiter(length);
michael@0 9890
michael@0 9891 let contact = null;
michael@0 9892 if (alphaId || number) {
michael@0 9893 contact = {alphaId: alphaId,
michael@0 9894 number: number};
michael@0 9895 }
michael@0 9896 return contact;
michael@0 9897 },
michael@0 9898
michael@0 9899 /**
michael@0 9900 * Write Alpha Identifier and Dialling number from TS 151.011 clause 10.5.1
michael@0 9901 *
michael@0 9902 * @param recordSize The size of linear fixed record.
michael@0 9903 * @param alphaId Alpha Identifier to be written.
michael@0 9904 * @param number Dialling Number to be written.
michael@0 9905 */
michael@0 9906 writeAlphaIdDiallingNumber: function(recordSize, alphaId, number) {
michael@0 9907 let Buf = this.context.Buf;
michael@0 9908 let GsmPDUHelper = this.context.GsmPDUHelper;
michael@0 9909
michael@0 9910 // Write String length
michael@0 9911 let strLen = recordSize * 2;
michael@0 9912 Buf.writeInt32(strLen);
michael@0 9913
michael@0 9914 let alphaLen = recordSize - ADN_FOOTER_SIZE_BYTES;
michael@0 9915 this.writeAlphaIdentifier(alphaLen, alphaId);
michael@0 9916 this.writeNumberWithLength(number);
michael@0 9917
michael@0 9918 // Write unused octets 0xff, CCP and EXT1.
michael@0 9919 GsmPDUHelper.writeHexOctet(0xff);
michael@0 9920 GsmPDUHelper.writeHexOctet(0xff);
michael@0 9921 Buf.writeStringDelimiter(strLen);
michael@0 9922 },
michael@0 9923
michael@0 9924 /**
michael@0 9925 * Read Alpha Identifier.
michael@0 9926 *
michael@0 9927 * @see TS 131.102
michael@0 9928 *
michael@0 9929 * @param numOctets
michael@0 9930 * Number of octets to be read.
michael@0 9931 *
michael@0 9932 * It uses either
michael@0 9933 * 1. SMS default 7-bit alphabet with bit 8 set to 0.
michael@0 9934 * 2. UCS2 string.
michael@0 9935 *
michael@0 9936 * Unused bytes should be set to 0xff.
michael@0 9937 */
michael@0 9938 readAlphaIdentifier: function(numOctets) {
michael@0 9939 if (numOctets === 0) {
michael@0 9940 return "";
michael@0 9941 }
michael@0 9942
michael@0 9943 let temp;
michael@0 9944 // Read the 1st octet to determine the encoding.
michael@0 9945 if ((temp = this.context.GsmPDUHelper.readHexOctet()) == 0x80 ||
michael@0 9946 temp == 0x81 ||
michael@0 9947 temp == 0x82) {
michael@0 9948 numOctets--;
michael@0 9949 return this.readICCUCS2String(temp, numOctets);
michael@0 9950 } else {
michael@0 9951 let Buf = this.context.Buf;
michael@0 9952 Buf.seekIncoming(-1 * Buf.PDU_HEX_OCTET_SIZE);
michael@0 9953 return this.read8BitUnpackedToString(numOctets);
michael@0 9954 }
michael@0 9955 },
michael@0 9956
michael@0 9957 /**
michael@0 9958 * Write Alpha Identifier.
michael@0 9959 *
michael@0 9960 * @param numOctets
michael@0 9961 * Total number of octets to be written. This includes the length of
michael@0 9962 * alphaId and the length of trailing unused octets(0xff).
michael@0 9963 * @param alphaId
michael@0 9964 * Alpha Identifier to be written.
michael@0 9965 *
michael@0 9966 * Unused octets will be written as 0xff.
michael@0 9967 */
michael@0 9968 writeAlphaIdentifier: function(numOctets, alphaId) {
michael@0 9969 if (numOctets === 0) {
michael@0 9970 return;
michael@0 9971 }
michael@0 9972
michael@0 9973 // If alphaId is empty or it's of GSM 8 bit.
michael@0 9974 if (!alphaId || this.context.ICCUtilsHelper.isGsm8BitAlphabet(alphaId)) {
michael@0 9975 this.writeStringTo8BitUnpacked(numOctets, alphaId);
michael@0 9976 } else {
michael@0 9977 let GsmPDUHelper = this.context.GsmPDUHelper;
michael@0 9978
michael@0 9979 // Currently only support UCS2 coding scheme 0x80.
michael@0 9980 GsmPDUHelper.writeHexOctet(0x80);
michael@0 9981 numOctets--;
michael@0 9982 // Now the alphaId is UCS2 string, each character will take 2 octets.
michael@0 9983 if (alphaId.length * 2 > numOctets) {
michael@0 9984 alphaId = alphaId.substring(0, Math.floor(numOctets / 2));
michael@0 9985 }
michael@0 9986 GsmPDUHelper.writeUCS2String(alphaId);
michael@0 9987 for (let i = alphaId.length * 2; i < numOctets; i++) {
michael@0 9988 GsmPDUHelper.writeHexOctet(0xff);
michael@0 9989 }
michael@0 9990 }
michael@0 9991 },
michael@0 9992
michael@0 9993 /**
michael@0 9994 * Read Dialling number.
michael@0 9995 *
michael@0 9996 * @see TS 131.102
michael@0 9997 *
michael@0 9998 * @param len
michael@0 9999 * The Length of BCD number.
michael@0 10000 *
michael@0 10001 * From TS 131.102, in EF_ADN, EF_FDN, the field 'Length of BCD number'
michael@0 10002 * means the total bytes should be allocated to store the TON/NPI and
michael@0 10003 * the dialing number.
michael@0 10004 * For example, if the dialing number is 1234567890,
michael@0 10005 * and the TON/NPI is 0x81,
michael@0 10006 * The field 'Length of BCD number' should be 06, which is
michael@0 10007 * 1 byte to store the TON/NPI, 0x81
michael@0 10008 * 5 bytes to store the BCD number 2143658709.
michael@0 10009 *
michael@0 10010 * Here the definition of the length is different from SMS spec,
michael@0 10011 * TS 23.040 9.1.2.5, which the length means
michael@0 10012 * "number of useful semi-octets within the Address-Value field".
michael@0 10013 */
michael@0 10014 readDiallingNumber: function(len) {
michael@0 10015 if (DEBUG) this.context.debug("PDU: Going to read Dialling number: " + len);
michael@0 10016 if (len === 0) {
michael@0 10017 return "";
michael@0 10018 }
michael@0 10019
michael@0 10020 let GsmPDUHelper = this.context.GsmPDUHelper;
michael@0 10021
michael@0 10022 // TOA = TON + NPI
michael@0 10023 let toa = GsmPDUHelper.readHexOctet();
michael@0 10024
michael@0 10025 let number = GsmPDUHelper.readSwappedNibbleBcdString(len - 1);
michael@0 10026 if (number.length <= 0) {
michael@0 10027 if (DEBUG) this.context.debug("No number provided");
michael@0 10028 return "";
michael@0 10029 }
michael@0 10030 if ((toa >> 4) == (PDU_TOA_INTERNATIONAL >> 4)) {
michael@0 10031 number = '+' + number;
michael@0 10032 }
michael@0 10033 return number;
michael@0 10034 },
michael@0 10035
michael@0 10036 /**
michael@0 10037 * Write Dialling Number.
michael@0 10038 *
michael@0 10039 * @param number The Dialling number
michael@0 10040 */
michael@0 10041 writeDiallingNumber: function(number) {
michael@0 10042 let GsmPDUHelper = this.context.GsmPDUHelper;
michael@0 10043
michael@0 10044 let toa = PDU_TOA_ISDN; // 81
michael@0 10045 if (number[0] == '+') {
michael@0 10046 toa = PDU_TOA_INTERNATIONAL | PDU_TOA_ISDN; // 91
michael@0 10047 number = number.substring(1);
michael@0 10048 }
michael@0 10049 GsmPDUHelper.writeHexOctet(toa);
michael@0 10050 GsmPDUHelper.writeSwappedNibbleBCD(number);
michael@0 10051 },
michael@0 10052
michael@0 10053 readNumberWithLength: function() {
michael@0 10054 let Buf = this.context.Buf;
michael@0 10055 let number;
michael@0 10056 let numLen = this.context.GsmPDUHelper.readHexOctet();
michael@0 10057 if (numLen != 0xff) {
michael@0 10058 if (numLen > ADN_MAX_BCD_NUMBER_BYTES) {
michael@0 10059 throw new Error("invalid length of BCD number/SSC contents - " + numLen);
michael@0 10060 }
michael@0 10061
michael@0 10062 number = this.readDiallingNumber(numLen);
michael@0 10063 Buf.seekIncoming((ADN_MAX_BCD_NUMBER_BYTES - numLen) * Buf.PDU_HEX_OCTET_SIZE);
michael@0 10064 } else {
michael@0 10065 Buf.seekIncoming(ADN_MAX_BCD_NUMBER_BYTES * Buf.PDU_HEX_OCTET_SIZE);
michael@0 10066 }
michael@0 10067
michael@0 10068 return number;
michael@0 10069 },
michael@0 10070
michael@0 10071 writeNumberWithLength: function(number) {
michael@0 10072 let GsmPDUHelper = this.context.GsmPDUHelper;
michael@0 10073
michael@0 10074 if (number) {
michael@0 10075 let numStart = number[0] == "+" ? 1 : 0;
michael@0 10076 number = number.substring(0, numStart) +
michael@0 10077 number.substring(numStart)
michael@0 10078 .replace(/[^0-9*#,]/g, "")
michael@0 10079 .replace(/\*/g, "a")
michael@0 10080 .replace(/\#/g, "b")
michael@0 10081 .replace(/\,/g, "c");
michael@0 10082
michael@0 10083 let numDigits = number.length - numStart;
michael@0 10084 if (numDigits > ADN_MAX_NUMBER_DIGITS) {
michael@0 10085 number = number.substring(0, ADN_MAX_NUMBER_DIGITS + numStart);
michael@0 10086 numDigits = number.length - numStart;
michael@0 10087 }
michael@0 10088
michael@0 10089 // +1 for TON/NPI
michael@0 10090 let numLen = Math.ceil(numDigits / 2) + 1;
michael@0 10091 GsmPDUHelper.writeHexOctet(numLen);
michael@0 10092 this.writeDiallingNumber(number);
michael@0 10093 // Write trailing 0xff of Dialling Number.
michael@0 10094 for (let i = 0; i < ADN_MAX_BCD_NUMBER_BYTES - numLen; i++) {
michael@0 10095 GsmPDUHelper.writeHexOctet(0xff);
michael@0 10096 }
michael@0 10097 } else {
michael@0 10098 // +1 for numLen
michael@0 10099 for (let i = 0; i < ADN_MAX_BCD_NUMBER_BYTES + 1; i++) {
michael@0 10100 GsmPDUHelper.writeHexOctet(0xff);
michael@0 10101 }
michael@0 10102 }
michael@0 10103 }
michael@0 10104 };
michael@0 10105
michael@0 10106 function StkCommandParamsFactoryObject(aContext) {
michael@0 10107 this.context = aContext;
michael@0 10108 }
michael@0 10109 StkCommandParamsFactoryObject.prototype = {
michael@0 10110 context: null,
michael@0 10111
michael@0 10112 createParam: function(cmdDetails, ctlvs) {
michael@0 10113 let method = this[cmdDetails.typeOfCommand];
michael@0 10114 if (typeof method != "function") {
michael@0 10115 if (DEBUG) {
michael@0 10116 this.context.debug("Unknown proactive command " +
michael@0 10117 cmdDetails.typeOfCommand.toString(16));
michael@0 10118 }
michael@0 10119 return null;
michael@0 10120 }
michael@0 10121 return method.call(this, cmdDetails, ctlvs);
michael@0 10122 },
michael@0 10123
michael@0 10124 /**
michael@0 10125 * Construct a param for Refresh.
michael@0 10126 *
michael@0 10127 * @param cmdDetails
michael@0 10128 * The value object of CommandDetails TLV.
michael@0 10129 * @param ctlvs
michael@0 10130 * The all TLVs in this proactive command.
michael@0 10131 */
michael@0 10132 processRefresh: function(cmdDetails, ctlvs) {
michael@0 10133 let refreshType = cmdDetails.commandQualifier;
michael@0 10134 switch (refreshType) {
michael@0 10135 case STK_REFRESH_FILE_CHANGE:
michael@0 10136 case STK_REFRESH_NAA_INIT_AND_FILE_CHANGE:
michael@0 10137 let ctlv = this.context.StkProactiveCmdHelper.searchForTag(
michael@0 10138 COMPREHENSIONTLV_TAG_FILE_LIST, ctlvs);
michael@0 10139 if (ctlv) {
michael@0 10140 let list = ctlv.value.fileList;
michael@0 10141 if (DEBUG) {
michael@0 10142 this.context.debug("Refresh, list = " + list);
michael@0 10143 }
michael@0 10144 this.context.ICCRecordHelper.fetchICCRecords();
michael@0 10145 }
michael@0 10146 break;
michael@0 10147 }
michael@0 10148 return null;
michael@0 10149 },
michael@0 10150
michael@0 10151 /**
michael@0 10152 * Construct a param for Poll Interval.
michael@0 10153 *
michael@0 10154 * @param cmdDetails
michael@0 10155 * The value object of CommandDetails TLV.
michael@0 10156 * @param ctlvs
michael@0 10157 * The all TLVs in this proactive command.
michael@0 10158 */
michael@0 10159 processPollInterval: function(cmdDetails, ctlvs) {
michael@0 10160 let ctlv = this.context.StkProactiveCmdHelper.searchForTag(
michael@0 10161 COMPREHENSIONTLV_TAG_DURATION, ctlvs);
michael@0 10162 if (!ctlv) {
michael@0 10163 this.context.RIL.sendStkTerminalResponse({
michael@0 10164 command: cmdDetails,
michael@0 10165 resultCode: STK_RESULT_REQUIRED_VALUES_MISSING});
michael@0 10166 throw new Error("Stk Poll Interval: Required value missing : Duration");
michael@0 10167 }
michael@0 10168
michael@0 10169 return ctlv.value;
michael@0 10170 },
michael@0 10171
michael@0 10172 /**
michael@0 10173 * Construct a param for Poll Off.
michael@0 10174 *
michael@0 10175 * @param cmdDetails
michael@0 10176 * The value object of CommandDetails TLV.
michael@0 10177 * @param ctlvs
michael@0 10178 * The all TLVs in this proactive command.
michael@0 10179 */
michael@0 10180 processPollOff: function(cmdDetails, ctlvs) {
michael@0 10181 return null;
michael@0 10182 },
michael@0 10183
michael@0 10184 /**
michael@0 10185 * Construct a param for Set Up Event list.
michael@0 10186 *
michael@0 10187 * @param cmdDetails
michael@0 10188 * The value object of CommandDetails TLV.
michael@0 10189 * @param ctlvs
michael@0 10190 * The all TLVs in this proactive command.
michael@0 10191 */
michael@0 10192 processSetUpEventList: function(cmdDetails, ctlvs) {
michael@0 10193 let ctlv = this.context.StkProactiveCmdHelper.searchForTag(
michael@0 10194 COMPREHENSIONTLV_TAG_EVENT_LIST, ctlvs);
michael@0 10195 if (!ctlv) {
michael@0 10196 this.context.RIL.sendStkTerminalResponse({
michael@0 10197 command: cmdDetails,
michael@0 10198 resultCode: STK_RESULT_REQUIRED_VALUES_MISSING});
michael@0 10199 throw new Error("Stk Event List: Required value missing : Event List");
michael@0 10200 }
michael@0 10201
michael@0 10202 return ctlv.value || {eventList: null};
michael@0 10203 },
michael@0 10204
michael@0 10205 /**
michael@0 10206 * Construct a param for Select Item.
michael@0 10207 *
michael@0 10208 * @param cmdDetails
michael@0 10209 * The value object of CommandDetails TLV.
michael@0 10210 * @param ctlvs
michael@0 10211 * The all TLVs in this proactive command.
michael@0 10212 */
michael@0 10213 processSelectItem: function(cmdDetails, ctlvs) {
michael@0 10214 let StkProactiveCmdHelper = this.context.StkProactiveCmdHelper;
michael@0 10215 let menu = {};
michael@0 10216
michael@0 10217 let ctlv = StkProactiveCmdHelper.searchForTag(COMPREHENSIONTLV_TAG_ALPHA_ID, ctlvs);
michael@0 10218 if (ctlv) {
michael@0 10219 menu.title = ctlv.value.identifier;
michael@0 10220 }
michael@0 10221
michael@0 10222 menu.items = [];
michael@0 10223 for (let i = 0; i < ctlvs.length; i++) {
michael@0 10224 let ctlv = ctlvs[i];
michael@0 10225 if (ctlv.tag == COMPREHENSIONTLV_TAG_ITEM) {
michael@0 10226 menu.items.push(ctlv.value);
michael@0 10227 }
michael@0 10228 }
michael@0 10229
michael@0 10230 if (menu.items.length === 0) {
michael@0 10231 this.context.RIL.sendStkTerminalResponse({
michael@0 10232 command: cmdDetails,
michael@0 10233 resultCode: STK_RESULT_REQUIRED_VALUES_MISSING});
michael@0 10234 throw new Error("Stk Menu: Required value missing : items");
michael@0 10235 }
michael@0 10236
michael@0 10237 ctlv = StkProactiveCmdHelper.searchForTag(COMPREHENSIONTLV_TAG_ITEM_ID, ctlvs);
michael@0 10238 if (ctlv) {
michael@0 10239 menu.defaultItem = ctlv.value.identifier - 1;
michael@0 10240 }
michael@0 10241
michael@0 10242 // The 1st bit and 2nd bit determines the presentation type.
michael@0 10243 menu.presentationType = cmdDetails.commandQualifier & 0x03;
michael@0 10244
michael@0 10245 // Help information available.
michael@0 10246 if (cmdDetails.commandQualifier & 0x80) {
michael@0 10247 menu.isHelpAvailable = true;
michael@0 10248 }
michael@0 10249
michael@0 10250 ctlv = StkProactiveCmdHelper.searchForTag(COMPREHENSIONTLV_TAG_NEXT_ACTION_IND, ctlvs);
michael@0 10251 if (ctlv) {
michael@0 10252 menu.nextActionList = ctlv.value;
michael@0 10253 }
michael@0 10254
michael@0 10255 return menu;
michael@0 10256 },
michael@0 10257
michael@0 10258 processDisplayText: function(cmdDetails, ctlvs) {
michael@0 10259 let StkProactiveCmdHelper = this.context.StkProactiveCmdHelper;
michael@0 10260 let textMsg = {};
michael@0 10261
michael@0 10262 let ctlv = StkProactiveCmdHelper.searchForTag(COMPREHENSIONTLV_TAG_TEXT_STRING, ctlvs);
michael@0 10263 if (!ctlv) {
michael@0 10264 this.context.RIL.sendStkTerminalResponse({
michael@0 10265 command: cmdDetails,
michael@0 10266 resultCode: STK_RESULT_REQUIRED_VALUES_MISSING});
michael@0 10267 throw new Error("Stk Display Text: Required value missing : Text String");
michael@0 10268 }
michael@0 10269 textMsg.text = ctlv.value.textString;
michael@0 10270
michael@0 10271 ctlv = StkProactiveCmdHelper.searchForTag(COMPREHENSIONTLV_TAG_IMMEDIATE_RESPONSE, ctlvs);
michael@0 10272 if (ctlv) {
michael@0 10273 textMsg.responseNeeded = true;
michael@0 10274 }
michael@0 10275
michael@0 10276 ctlv = StkProactiveCmdHelper.searchForTag(COMPREHENSIONTLV_TAG_DURATION, ctlvs);
michael@0 10277 if (ctlv) {
michael@0 10278 textMsg.duration = ctlv.value;
michael@0 10279 }
michael@0 10280
michael@0 10281 // High priority.
michael@0 10282 if (cmdDetails.commandQualifier & 0x01) {
michael@0 10283 textMsg.isHighPriority = true;
michael@0 10284 }
michael@0 10285
michael@0 10286 // User clear.
michael@0 10287 if (cmdDetails.commandQualifier & 0x80) {
michael@0 10288 textMsg.userClear = true;
michael@0 10289 }
michael@0 10290
michael@0 10291 return textMsg;
michael@0 10292 },
michael@0 10293
michael@0 10294 processSetUpIdleModeText: function(cmdDetails, ctlvs) {
michael@0 10295 let textMsg = {};
michael@0 10296
michael@0 10297 let ctlv = this.context.StkProactiveCmdHelper.searchForTag(
michael@0 10298 COMPREHENSIONTLV_TAG_TEXT_STRING, ctlvs);
michael@0 10299 if (!ctlv) {
michael@0 10300 this.context.RIL.sendStkTerminalResponse({
michael@0 10301 command: cmdDetails,
michael@0 10302 resultCode: STK_RESULT_REQUIRED_VALUES_MISSING});
michael@0 10303 throw new Error("Stk Set Up Idle Text: Required value missing : Text String");
michael@0 10304 }
michael@0 10305 textMsg.text = ctlv.value.textString;
michael@0 10306
michael@0 10307 return textMsg;
michael@0 10308 },
michael@0 10309
michael@0 10310 processGetInkey: function(cmdDetails, ctlvs) {
michael@0 10311 let StkProactiveCmdHelper = this.context.StkProactiveCmdHelper;
michael@0 10312 let input = {};
michael@0 10313
michael@0 10314 let ctlv = StkProactiveCmdHelper.searchForTag(COMPREHENSIONTLV_TAG_TEXT_STRING, ctlvs);
michael@0 10315 if (!ctlv) {
michael@0 10316 this.context.RIL.sendStkTerminalResponse({
michael@0 10317 command: cmdDetails,
michael@0 10318 resultCode: STK_RESULT_REQUIRED_VALUES_MISSING});
michael@0 10319 throw new Error("Stk Get InKey: Required value missing : Text String");
michael@0 10320 }
michael@0 10321 input.text = ctlv.value.textString;
michael@0 10322
michael@0 10323 // duration
michael@0 10324 ctlv = StkProactiveCmdHelper.searchForTag(
michael@0 10325 COMPREHENSIONTLV_TAG_DURATION, ctlvs);
michael@0 10326 if (ctlv) {
michael@0 10327 input.duration = ctlv.value;
michael@0 10328 }
michael@0 10329
michael@0 10330 input.minLength = 1;
michael@0 10331 input.maxLength = 1;
michael@0 10332
michael@0 10333 // isAlphabet
michael@0 10334 if (cmdDetails.commandQualifier & 0x01) {
michael@0 10335 input.isAlphabet = true;
michael@0 10336 }
michael@0 10337
michael@0 10338 // UCS2
michael@0 10339 if (cmdDetails.commandQualifier & 0x02) {
michael@0 10340 input.isUCS2 = true;
michael@0 10341 }
michael@0 10342
michael@0 10343 // Character sets defined in bit 1 and bit 2 are disable and
michael@0 10344 // the YES/NO reponse is required.
michael@0 10345 if (cmdDetails.commandQualifier & 0x04) {
michael@0 10346 input.isYesNoRequested = true;
michael@0 10347 }
michael@0 10348
michael@0 10349 // Help information available.
michael@0 10350 if (cmdDetails.commandQualifier & 0x80) {
michael@0 10351 input.isHelpAvailable = true;
michael@0 10352 }
michael@0 10353
michael@0 10354 return input;
michael@0 10355 },
michael@0 10356
michael@0 10357 processGetInput: function(cmdDetails, ctlvs) {
michael@0 10358 let StkProactiveCmdHelper = this.context.StkProactiveCmdHelper;
michael@0 10359 let input = {};
michael@0 10360
michael@0 10361 let ctlv = StkProactiveCmdHelper.searchForTag(COMPREHENSIONTLV_TAG_TEXT_STRING, ctlvs);
michael@0 10362 if (!ctlv) {
michael@0 10363 this.context.RIL.sendStkTerminalResponse({
michael@0 10364 command: cmdDetails,
michael@0 10365 resultCode: STK_RESULT_REQUIRED_VALUES_MISSING});
michael@0 10366 throw new Error("Stk Get Input: Required value missing : Text String");
michael@0 10367 }
michael@0 10368 input.text = ctlv.value.textString;
michael@0 10369
michael@0 10370 ctlv = StkProactiveCmdHelper.searchForTag(COMPREHENSIONTLV_TAG_RESPONSE_LENGTH, ctlvs);
michael@0 10371 if (ctlv) {
michael@0 10372 input.minLength = ctlv.value.minLength;
michael@0 10373 input.maxLength = ctlv.value.maxLength;
michael@0 10374 }
michael@0 10375
michael@0 10376 ctlv = StkProactiveCmdHelper.searchForTag(COMPREHENSIONTLV_TAG_DEFAULT_TEXT, ctlvs);
michael@0 10377 if (ctlv) {
michael@0 10378 input.defaultText = ctlv.value.textString;
michael@0 10379 }
michael@0 10380
michael@0 10381 // Alphabet only
michael@0 10382 if (cmdDetails.commandQualifier & 0x01) {
michael@0 10383 input.isAlphabet = true;
michael@0 10384 }
michael@0 10385
michael@0 10386 // UCS2
michael@0 10387 if (cmdDetails.commandQualifier & 0x02) {
michael@0 10388 input.isUCS2 = true;
michael@0 10389 }
michael@0 10390
michael@0 10391 // User input shall not be revealed
michael@0 10392 if (cmdDetails.commandQualifier & 0x04) {
michael@0 10393 input.hideInput = true;
michael@0 10394 }
michael@0 10395
michael@0 10396 // User input in SMS packed format
michael@0 10397 if (cmdDetails.commandQualifier & 0x08) {
michael@0 10398 input.isPacked = true;
michael@0 10399 }
michael@0 10400
michael@0 10401 // Help information available.
michael@0 10402 if (cmdDetails.commandQualifier & 0x80) {
michael@0 10403 input.isHelpAvailable = true;
michael@0 10404 }
michael@0 10405
michael@0 10406 return input;
michael@0 10407 },
michael@0 10408
michael@0 10409 processEventNotify: function(cmdDetails, ctlvs) {
michael@0 10410 let textMsg = {};
michael@0 10411
michael@0 10412 let ctlv = this.context.StkProactiveCmdHelper.searchForTag(
michael@0 10413 COMPREHENSIONTLV_TAG_ALPHA_ID, ctlvs);
michael@0 10414 if (!ctlv) {
michael@0 10415 this.context.RIL.sendStkTerminalResponse({
michael@0 10416 command: cmdDetails,
michael@0 10417 resultCode: STK_RESULT_REQUIRED_VALUES_MISSING});
michael@0 10418 throw new Error("Stk Event Notfiy: Required value missing : Alpha ID");
michael@0 10419 }
michael@0 10420 textMsg.text = ctlv.value.identifier;
michael@0 10421
michael@0 10422 return textMsg;
michael@0 10423 },
michael@0 10424
michael@0 10425 processSetupCall: function(cmdDetails, ctlvs) {
michael@0 10426 let StkProactiveCmdHelper = this.context.StkProactiveCmdHelper;
michael@0 10427 let call = {};
michael@0 10428 let iter = Iterator(ctlvs);
michael@0 10429
michael@0 10430 let ctlv = StkProactiveCmdHelper.searchForNextTag(COMPREHENSIONTLV_TAG_ALPHA_ID, iter);
michael@0 10431 if (ctlv) {
michael@0 10432 call.confirmMessage = ctlv.value.identifier;
michael@0 10433 }
michael@0 10434
michael@0 10435 ctlv = StkProactiveCmdHelper.searchForNextTag(COMPREHENSIONTLV_TAG_ALPHA_ID, iter);
michael@0 10436 if (ctlv) {
michael@0 10437 call.callMessage = ctlv.value.identifier;
michael@0 10438 }
michael@0 10439
michael@0 10440 ctlv = StkProactiveCmdHelper.searchForTag(COMPREHENSIONTLV_TAG_ADDRESS, ctlvs);
michael@0 10441 if (!ctlv) {
michael@0 10442 this.context.RIL.sendStkTerminalResponse({
michael@0 10443 command: cmdDetails,
michael@0 10444 resultCode: STK_RESULT_REQUIRED_VALUES_MISSING});
michael@0 10445 throw new Error("Stk Set Up Call: Required value missing : Adress");
michael@0 10446 }
michael@0 10447 call.address = ctlv.value.number;
michael@0 10448
michael@0 10449 // see 3GPP TS 31.111 section 6.4.13
michael@0 10450 ctlv = StkProactiveCmdHelper.searchForTag(COMPREHENSIONTLV_TAG_DURATION, ctlvs);
michael@0 10451 if (ctlv) {
michael@0 10452 call.duration = ctlv.value;
michael@0 10453 }
michael@0 10454
michael@0 10455 return call;
michael@0 10456 },
michael@0 10457
michael@0 10458 processLaunchBrowser: function(cmdDetails, ctlvs) {
michael@0 10459 let StkProactiveCmdHelper = this.context.StkProactiveCmdHelper;
michael@0 10460 let browser = {};
michael@0 10461
michael@0 10462 let ctlv = StkProactiveCmdHelper.searchForTag(COMPREHENSIONTLV_TAG_URL, ctlvs);
michael@0 10463 if (!ctlv) {
michael@0 10464 this.context.RIL.sendStkTerminalResponse({
michael@0 10465 command: cmdDetails,
michael@0 10466 resultCode: STK_RESULT_REQUIRED_VALUES_MISSING});
michael@0 10467 throw new Error("Stk Launch Browser: Required value missing : URL");
michael@0 10468 }
michael@0 10469 browser.url = ctlv.value.url;
michael@0 10470
michael@0 10471 ctlv = StkProactiveCmdHelper.searchForTag(COMPREHENSIONTLV_TAG_ALPHA_ID, ctlvs);
michael@0 10472 if (ctlv) {
michael@0 10473 browser.confirmMessage = ctlv.value.identifier;
michael@0 10474 }
michael@0 10475
michael@0 10476 browser.mode = cmdDetails.commandQualifier & 0x03;
michael@0 10477
michael@0 10478 return browser;
michael@0 10479 },
michael@0 10480
michael@0 10481 processPlayTone: function(cmdDetails, ctlvs) {
michael@0 10482 let StkProactiveCmdHelper = this.context.StkProactiveCmdHelper;
michael@0 10483 let playTone = {};
michael@0 10484
michael@0 10485 let ctlv = StkProactiveCmdHelper.searchForTag(
michael@0 10486 COMPREHENSIONTLV_TAG_ALPHA_ID, ctlvs);
michael@0 10487 if (ctlv) {
michael@0 10488 playTone.text = ctlv.value.identifier;
michael@0 10489 }
michael@0 10490
michael@0 10491 ctlv = StkProactiveCmdHelper.searchForTag(COMPREHENSIONTLV_TAG_TONE, ctlvs);
michael@0 10492 if (ctlv) {
michael@0 10493 playTone.tone = ctlv.value.tone;
michael@0 10494 }
michael@0 10495
michael@0 10496 ctlv = StkProactiveCmdHelper.searchForTag(
michael@0 10497 COMPREHENSIONTLV_TAG_DURATION, ctlvs);
michael@0 10498 if (ctlv) {
michael@0 10499 playTone.duration = ctlv.value;
michael@0 10500 }
michael@0 10501
michael@0 10502 // vibrate is only defined in TS 102.223
michael@0 10503 playTone.isVibrate = (cmdDetails.commandQualifier & 0x01) !== 0x00;
michael@0 10504
michael@0 10505 return playTone;
michael@0 10506 },
michael@0 10507
michael@0 10508 /**
michael@0 10509 * Construct a param for Provide Local Information
michael@0 10510 *
michael@0 10511 * @param cmdDetails
michael@0 10512 * The value object of CommandDetails TLV.
michael@0 10513 * @param ctlvs
michael@0 10514 * The all TLVs in this proactive command.
michael@0 10515 */
michael@0 10516 processProvideLocalInfo: function(cmdDetails, ctlvs) {
michael@0 10517 let provideLocalInfo = {
michael@0 10518 localInfoType: cmdDetails.commandQualifier
michael@0 10519 };
michael@0 10520 return provideLocalInfo;
michael@0 10521 },
michael@0 10522
michael@0 10523 processTimerManagement: function(cmdDetails, ctlvs) {
michael@0 10524 let StkProactiveCmdHelper = this.context.StkProactiveCmdHelper;
michael@0 10525 let timer = {
michael@0 10526 timerAction: cmdDetails.commandQualifier
michael@0 10527 };
michael@0 10528
michael@0 10529 let ctlv = StkProactiveCmdHelper.searchForTag(
michael@0 10530 COMPREHENSIONTLV_TAG_TIMER_IDENTIFIER, ctlvs);
michael@0 10531 if (ctlv) {
michael@0 10532 timer.timerId = ctlv.value.timerId;
michael@0 10533 }
michael@0 10534
michael@0 10535 ctlv = StkProactiveCmdHelper.searchForTag(
michael@0 10536 COMPREHENSIONTLV_TAG_TIMER_VALUE, ctlvs);
michael@0 10537 if (ctlv) {
michael@0 10538 timer.timerValue = ctlv.value.timerValue;
michael@0 10539 }
michael@0 10540
michael@0 10541 return timer;
michael@0 10542 },
michael@0 10543
michael@0 10544 /**
michael@0 10545 * Construct a param for BIP commands.
michael@0 10546 *
michael@0 10547 * @param cmdDetails
michael@0 10548 * The value object of CommandDetails TLV.
michael@0 10549 * @param ctlvs
michael@0 10550 * The all TLVs in this proactive command.
michael@0 10551 */
michael@0 10552 processBipMessage: function(cmdDetails, ctlvs) {
michael@0 10553 let bipMsg = {};
michael@0 10554
michael@0 10555 let ctlv = this.context.StkProactiveCmdHelper.searchForTag(
michael@0 10556 COMPREHENSIONTLV_TAG_ALPHA_ID, ctlvs);
michael@0 10557 if (ctlv) {
michael@0 10558 bipMsg.text = ctlv.value.identifier;
michael@0 10559 }
michael@0 10560
michael@0 10561 return bipMsg;
michael@0 10562 }
michael@0 10563 };
michael@0 10564 StkCommandParamsFactoryObject.prototype[STK_CMD_REFRESH] = function STK_CMD_REFRESH(cmdDetails, ctlvs) {
michael@0 10565 return this.processRefresh(cmdDetails, ctlvs);
michael@0 10566 };
michael@0 10567 StkCommandParamsFactoryObject.prototype[STK_CMD_POLL_INTERVAL] = function STK_CMD_POLL_INTERVAL(cmdDetails, ctlvs) {
michael@0 10568 return this.processPollInterval(cmdDetails, ctlvs);
michael@0 10569 };
michael@0 10570 StkCommandParamsFactoryObject.prototype[STK_CMD_POLL_OFF] = function STK_CMD_POLL_OFF(cmdDetails, ctlvs) {
michael@0 10571 return this.processPollOff(cmdDetails, ctlvs);
michael@0 10572 };
michael@0 10573 StkCommandParamsFactoryObject.prototype[STK_CMD_PROVIDE_LOCAL_INFO] = function STK_CMD_PROVIDE_LOCAL_INFO(cmdDetails, ctlvs) {
michael@0 10574 return this.processProvideLocalInfo(cmdDetails, ctlvs);
michael@0 10575 };
michael@0 10576 StkCommandParamsFactoryObject.prototype[STK_CMD_SET_UP_EVENT_LIST] = function STK_CMD_SET_UP_EVENT_LIST(cmdDetails, ctlvs) {
michael@0 10577 return this.processSetUpEventList(cmdDetails, ctlvs);
michael@0 10578 };
michael@0 10579 StkCommandParamsFactoryObject.prototype[STK_CMD_SET_UP_MENU] = function STK_CMD_SET_UP_MENU(cmdDetails, ctlvs) {
michael@0 10580 return this.processSelectItem(cmdDetails, ctlvs);
michael@0 10581 };
michael@0 10582 StkCommandParamsFactoryObject.prototype[STK_CMD_SELECT_ITEM] = function STK_CMD_SELECT_ITEM(cmdDetails, ctlvs) {
michael@0 10583 return this.processSelectItem(cmdDetails, ctlvs);
michael@0 10584 };
michael@0 10585 StkCommandParamsFactoryObject.prototype[STK_CMD_DISPLAY_TEXT] = function STK_CMD_DISPLAY_TEXT(cmdDetails, ctlvs) {
michael@0 10586 return this.processDisplayText(cmdDetails, ctlvs);
michael@0 10587 };
michael@0 10588 StkCommandParamsFactoryObject.prototype[STK_CMD_SET_UP_IDLE_MODE_TEXT] = function STK_CMD_SET_UP_IDLE_MODE_TEXT(cmdDetails, ctlvs) {
michael@0 10589 return this.processSetUpIdleModeText(cmdDetails, ctlvs);
michael@0 10590 };
michael@0 10591 StkCommandParamsFactoryObject.prototype[STK_CMD_GET_INKEY] = function STK_CMD_GET_INKEY(cmdDetails, ctlvs) {
michael@0 10592 return this.processGetInkey(cmdDetails, ctlvs);
michael@0 10593 };
michael@0 10594 StkCommandParamsFactoryObject.prototype[STK_CMD_GET_INPUT] = function STK_CMD_GET_INPUT(cmdDetails, ctlvs) {
michael@0 10595 return this.processGetInput(cmdDetails, ctlvs);
michael@0 10596 };
michael@0 10597 StkCommandParamsFactoryObject.prototype[STK_CMD_SEND_SS] = function STK_CMD_SEND_SS(cmdDetails, ctlvs) {
michael@0 10598 return this.processEventNotify(cmdDetails, ctlvs);
michael@0 10599 };
michael@0 10600 StkCommandParamsFactoryObject.prototype[STK_CMD_SEND_USSD] = function STK_CMD_SEND_USSD(cmdDetails, ctlvs) {
michael@0 10601 return this.processEventNotify(cmdDetails, ctlvs);
michael@0 10602 };
michael@0 10603 StkCommandParamsFactoryObject.prototype[STK_CMD_SEND_SMS] = function STK_CMD_SEND_SMS(cmdDetails, ctlvs) {
michael@0 10604 return this.processEventNotify(cmdDetails, ctlvs);
michael@0 10605 };
michael@0 10606 StkCommandParamsFactoryObject.prototype[STK_CMD_SEND_DTMF] = function STK_CMD_SEND_DTMF(cmdDetails, ctlvs) {
michael@0 10607 return this.processEventNotify(cmdDetails, ctlvs);
michael@0 10608 };
michael@0 10609 StkCommandParamsFactoryObject.prototype[STK_CMD_SET_UP_CALL] = function STK_CMD_SET_UP_CALL(cmdDetails, ctlvs) {
michael@0 10610 return this.processSetupCall(cmdDetails, ctlvs);
michael@0 10611 };
michael@0 10612 StkCommandParamsFactoryObject.prototype[STK_CMD_LAUNCH_BROWSER] = function STK_CMD_LAUNCH_BROWSER(cmdDetails, ctlvs) {
michael@0 10613 return this.processLaunchBrowser(cmdDetails, ctlvs);
michael@0 10614 };
michael@0 10615 StkCommandParamsFactoryObject.prototype[STK_CMD_PLAY_TONE] = function STK_CMD_PLAY_TONE(cmdDetails, ctlvs) {
michael@0 10616 return this.processPlayTone(cmdDetails, ctlvs);
michael@0 10617 };
michael@0 10618 StkCommandParamsFactoryObject.prototype[STK_CMD_TIMER_MANAGEMENT] = function STK_CMD_TIMER_MANAGEMENT(cmdDetails, ctlvs) {
michael@0 10619 return this.processTimerManagement(cmdDetails, ctlvs);
michael@0 10620 };
michael@0 10621 StkCommandParamsFactoryObject.prototype[STK_CMD_OPEN_CHANNEL] = function STK_CMD_OPEN_CHANNEL(cmdDetails, ctlvs) {
michael@0 10622 return this.processBipMessage(cmdDetails, ctlvs);
michael@0 10623 };
michael@0 10624 StkCommandParamsFactoryObject.prototype[STK_CMD_CLOSE_CHANNEL] = function STK_CMD_CLOSE_CHANNEL(cmdDetails, ctlvs) {
michael@0 10625 return this.processBipMessage(cmdDetails, ctlvs);
michael@0 10626 };
michael@0 10627 StkCommandParamsFactoryObject.prototype[STK_CMD_RECEIVE_DATA] = function STK_CMD_RECEIVE_DATA(cmdDetails, ctlvs) {
michael@0 10628 return this.processBipMessage(cmdDetails, ctlvs);
michael@0 10629 };
michael@0 10630 StkCommandParamsFactoryObject.prototype[STK_CMD_SEND_DATA] = function STK_CMD_SEND_DATA(cmdDetails, ctlvs) {
michael@0 10631 return this.processBipMessage(cmdDetails, ctlvs);
michael@0 10632 };
michael@0 10633
michael@0 10634 function StkProactiveCmdHelperObject(aContext) {
michael@0 10635 this.context = aContext;
michael@0 10636 }
michael@0 10637 StkProactiveCmdHelperObject.prototype = {
michael@0 10638 context: null,
michael@0 10639
michael@0 10640 retrieve: function(tag, length) {
michael@0 10641 let method = this[tag];
michael@0 10642 if (typeof method != "function") {
michael@0 10643 if (DEBUG) {
michael@0 10644 this.context.debug("Unknown comprehension tag " + tag.toString(16));
michael@0 10645 }
michael@0 10646 let Buf = this.context.Buf;
michael@0 10647 Buf.seekIncoming(length * Buf.PDU_HEX_OCTET_SIZE);
michael@0 10648 return null;
michael@0 10649 }
michael@0 10650 return method.call(this, length);
michael@0 10651 },
michael@0 10652
michael@0 10653 /**
michael@0 10654 * Command Details.
michael@0 10655 *
michael@0 10656 * | Byte | Description | Length |
michael@0 10657 * | 1 | Command details Tag | 1 |
michael@0 10658 * | 2 | Length = 03 | 1 |
michael@0 10659 * | 3 | Command number | 1 |
michael@0 10660 * | 4 | Type of Command | 1 |
michael@0 10661 * | 5 | Command Qualifier | 1 |
michael@0 10662 */
michael@0 10663 retrieveCommandDetails: function(length) {
michael@0 10664 let GsmPDUHelper = this.context.GsmPDUHelper;
michael@0 10665 let cmdDetails = {
michael@0 10666 commandNumber: GsmPDUHelper.readHexOctet(),
michael@0 10667 typeOfCommand: GsmPDUHelper.readHexOctet(),
michael@0 10668 commandQualifier: GsmPDUHelper.readHexOctet()
michael@0 10669 };
michael@0 10670 return cmdDetails;
michael@0 10671 },
michael@0 10672
michael@0 10673 /**
michael@0 10674 * Device Identities.
michael@0 10675 *
michael@0 10676 * | Byte | Description | Length |
michael@0 10677 * | 1 | Device Identity Tag | 1 |
michael@0 10678 * | 2 | Length = 02 | 1 |
michael@0 10679 * | 3 | Source device Identity | 1 |
michael@0 10680 * | 4 | Destination device Id | 1 |
michael@0 10681 */
michael@0 10682 retrieveDeviceId: function(length) {
michael@0 10683 let GsmPDUHelper = this.context.GsmPDUHelper;
michael@0 10684 let deviceId = {
michael@0 10685 sourceId: GsmPDUHelper.readHexOctet(),
michael@0 10686 destinationId: GsmPDUHelper.readHexOctet()
michael@0 10687 };
michael@0 10688 return deviceId;
michael@0 10689 },
michael@0 10690
michael@0 10691 /**
michael@0 10692 * Alpha Identifier.
michael@0 10693 *
michael@0 10694 * | Byte | Description | Length |
michael@0 10695 * | 1 | Alpha Identifier Tag | 1 |
michael@0 10696 * | 2 ~ (Y-1)+2 | Length (X) | Y |
michael@0 10697 * | (Y-1)+3 ~ | Alpha identfier | X |
michael@0 10698 * | (Y-1)+X+2 | | |
michael@0 10699 */
michael@0 10700 retrieveAlphaId: function(length) {
michael@0 10701 let alphaId = {
michael@0 10702 identifier: this.context.ICCPDUHelper.readAlphaIdentifier(length)
michael@0 10703 };
michael@0 10704 return alphaId;
michael@0 10705 },
michael@0 10706
michael@0 10707 /**
michael@0 10708 * Duration.
michael@0 10709 *
michael@0 10710 * | Byte | Description | Length |
michael@0 10711 * | 1 | Response Length Tag | 1 |
michael@0 10712 * | 2 | Lenth = 02 | 1 |
michael@0 10713 * | 3 | Time unit | 1 |
michael@0 10714 * | 4 | Time interval | 1 |
michael@0 10715 */
michael@0 10716 retrieveDuration: function(length) {
michael@0 10717 let GsmPDUHelper = this.context.GsmPDUHelper;
michael@0 10718 let duration = {
michael@0 10719 timeUnit: GsmPDUHelper.readHexOctet(),
michael@0 10720 timeInterval: GsmPDUHelper.readHexOctet(),
michael@0 10721 };
michael@0 10722 return duration;
michael@0 10723 },
michael@0 10724
michael@0 10725 /**
michael@0 10726 * Address.
michael@0 10727 *
michael@0 10728 * | Byte | Description | Length |
michael@0 10729 * | 1 | Alpha Identifier Tag | 1 |
michael@0 10730 * | 2 ~ (Y-1)+2 | Length (X) | Y |
michael@0 10731 * | (Y-1)+3 | TON and NPI | 1 |
michael@0 10732 * | (Y-1)+4 ~ | Dialling number | X |
michael@0 10733 * | (Y-1)+X+2 | | |
michael@0 10734 */
michael@0 10735 retrieveAddress: function(length) {
michael@0 10736 let address = {
michael@0 10737 number : this.context.ICCPDUHelper.readDiallingNumber(length)
michael@0 10738 };
michael@0 10739 return address;
michael@0 10740 },
michael@0 10741
michael@0 10742 /**
michael@0 10743 * Text String.
michael@0 10744 *
michael@0 10745 * | Byte | Description | Length |
michael@0 10746 * | 1 | Text String Tag | 1 |
michael@0 10747 * | 2 ~ (Y-1)+2 | Length (X) | Y |
michael@0 10748 * | (Y-1)+3 | Data coding scheme | 1 |
michael@0 10749 * | (Y-1)+4~ | Text String | X |
michael@0 10750 * | (Y-1)+X+2 | | |
michael@0 10751 */
michael@0 10752 retrieveTextString: function(length) {
michael@0 10753 if (!length) {
michael@0 10754 // null string.
michael@0 10755 return {textString: null};
michael@0 10756 }
michael@0 10757
michael@0 10758 let GsmPDUHelper = this.context.GsmPDUHelper;
michael@0 10759 let text = {
michael@0 10760 codingScheme: GsmPDUHelper.readHexOctet()
michael@0 10761 };
michael@0 10762
michael@0 10763 length--; // -1 for the codingScheme.
michael@0 10764 switch (text.codingScheme & 0x0f) {
michael@0 10765 case STK_TEXT_CODING_GSM_7BIT_PACKED:
michael@0 10766 text.textString = GsmPDUHelper.readSeptetsToString(length * 8 / 7, 0, 0, 0);
michael@0 10767 break;
michael@0 10768 case STK_TEXT_CODING_GSM_8BIT:
michael@0 10769 text.textString =
michael@0 10770 this.context.ICCPDUHelper.read8BitUnpackedToString(length);
michael@0 10771 break;
michael@0 10772 case STK_TEXT_CODING_UCS2:
michael@0 10773 text.textString = GsmPDUHelper.readUCS2String(length);
michael@0 10774 break;
michael@0 10775 }
michael@0 10776 return text;
michael@0 10777 },
michael@0 10778
michael@0 10779 /**
michael@0 10780 * Tone.
michael@0 10781 *
michael@0 10782 * | Byte | Description | Length |
michael@0 10783 * | 1 | Tone Tag | 1 |
michael@0 10784 * | 2 | Lenth = 01 | 1 |
michael@0 10785 * | 3 | Tone | 1 |
michael@0 10786 */
michael@0 10787 retrieveTone: function(length) {
michael@0 10788 let tone = {
michael@0 10789 tone: this.context.GsmPDUHelper.readHexOctet(),
michael@0 10790 };
michael@0 10791 return tone;
michael@0 10792 },
michael@0 10793
michael@0 10794 /**
michael@0 10795 * Item.
michael@0 10796 *
michael@0 10797 * | Byte | Description | Length |
michael@0 10798 * | 1 | Item Tag | 1 |
michael@0 10799 * | 2 ~ (Y-1)+2 | Length (X) | Y |
michael@0 10800 * | (Y-1)+3 | Identifier of item | 1 |
michael@0 10801 * | (Y-1)+4 ~ | Text string of item | X |
michael@0 10802 * | (Y-1)+X+2 | | |
michael@0 10803 */
michael@0 10804 retrieveItem: function(length) {
michael@0 10805 // TS 102.223 ,clause 6.6.7 SET-UP MENU
michael@0 10806 // If the "Item data object for item 1" is a null data object
michael@0 10807 // (i.e. length = '00' and no value part), this is an indication to the ME
michael@0 10808 // to remove the existing menu from the menu system in the ME.
michael@0 10809 if (!length) {
michael@0 10810 return null;
michael@0 10811 }
michael@0 10812 let item = {
michael@0 10813 identifier: this.context.GsmPDUHelper.readHexOctet(),
michael@0 10814 text: this.context.ICCPDUHelper.readAlphaIdentifier(length - 1)
michael@0 10815 };
michael@0 10816 return item;
michael@0 10817 },
michael@0 10818
michael@0 10819 /**
michael@0 10820 * Item Identifier.
michael@0 10821 *
michael@0 10822 * | Byte | Description | Length |
michael@0 10823 * | 1 | Item Identifier Tag | 1 |
michael@0 10824 * | 2 | Lenth = 01 | 1 |
michael@0 10825 * | 3 | Identifier of Item chosen | 1 |
michael@0 10826 */
michael@0 10827 retrieveItemId: function(length) {
michael@0 10828 let itemId = {
michael@0 10829 identifier: this.context.GsmPDUHelper.readHexOctet()
michael@0 10830 };
michael@0 10831 return itemId;
michael@0 10832 },
michael@0 10833
michael@0 10834 /**
michael@0 10835 * Response Length.
michael@0 10836 *
michael@0 10837 * | Byte | Description | Length |
michael@0 10838 * | 1 | Response Length Tag | 1 |
michael@0 10839 * | 2 | Lenth = 02 | 1 |
michael@0 10840 * | 3 | Minimum length of response | 1 |
michael@0 10841 * | 4 | Maximum length of response | 1 |
michael@0 10842 */
michael@0 10843 retrieveResponseLength: function(length) {
michael@0 10844 let GsmPDUHelper = this.context.GsmPDUHelper;
michael@0 10845 let rspLength = {
michael@0 10846 minLength : GsmPDUHelper.readHexOctet(),
michael@0 10847 maxLength : GsmPDUHelper.readHexOctet()
michael@0 10848 };
michael@0 10849 return rspLength;
michael@0 10850 },
michael@0 10851
michael@0 10852 /**
michael@0 10853 * File List.
michael@0 10854 *
michael@0 10855 * | Byte | Description | Length |
michael@0 10856 * | 1 | File List Tag | 1 |
michael@0 10857 * | 2 ~ (Y-1)+2 | Length (X) | Y |
michael@0 10858 * | (Y-1)+3 | Number of files | 1 |
michael@0 10859 * | (Y-1)+4 ~ | Files | X |
michael@0 10860 * | (Y-1)+X+2 | | |
michael@0 10861 */
michael@0 10862 retrieveFileList: function(length) {
michael@0 10863 let num = this.context.GsmPDUHelper.readHexOctet();
michael@0 10864 let fileList = "";
michael@0 10865 length--; // -1 for the num octet.
michael@0 10866 for (let i = 0; i < 2 * length; i++) {
michael@0 10867 // Didn't use readHexOctet here,
michael@0 10868 // otherwise 0x00 will be "0", not "00"
michael@0 10869 fileList += String.fromCharCode(this.context.Buf.readUint16());
michael@0 10870 }
michael@0 10871 return {
michael@0 10872 fileList: fileList
michael@0 10873 };
michael@0 10874 },
michael@0 10875
michael@0 10876 /**
michael@0 10877 * Default Text.
michael@0 10878 *
michael@0 10879 * Same as Text String.
michael@0 10880 */
michael@0 10881 retrieveDefaultText: function(length) {
michael@0 10882 return this.retrieveTextString(length);
michael@0 10883 },
michael@0 10884
michael@0 10885 /**
michael@0 10886 * Event List.
michael@0 10887 */
michael@0 10888 retrieveEventList: function(length) {
michael@0 10889 if (!length) {
michael@0 10890 // null means an indication to ME to remove the existing list of events
michael@0 10891 // in ME.
michael@0 10892 return null;
michael@0 10893 }
michael@0 10894
michael@0 10895 let GsmPDUHelper = this.context.GsmPDUHelper;
michael@0 10896 let eventList = [];
michael@0 10897 for (let i = 0; i < length; i++) {
michael@0 10898 eventList.push(GsmPDUHelper.readHexOctet());
michael@0 10899 }
michael@0 10900 return {
michael@0 10901 eventList: eventList
michael@0 10902 };
michael@0 10903 },
michael@0 10904
michael@0 10905 /**
michael@0 10906 * Timer Identifier.
michael@0 10907 *
michael@0 10908 * | Byte | Description | Length |
michael@0 10909 * | 1 | Timer Identifier Tag | 1 |
michael@0 10910 * | 2 | Length = 01 | 1 |
michael@0 10911 * | 3 | Timer Identifier | 1 |
michael@0 10912 */
michael@0 10913 retrieveTimerId: function(length) {
michael@0 10914 let id = {
michael@0 10915 timerId: this.context.GsmPDUHelper.readHexOctet()
michael@0 10916 };
michael@0 10917 return id;
michael@0 10918 },
michael@0 10919
michael@0 10920 /**
michael@0 10921 * Timer Value.
michael@0 10922 *
michael@0 10923 * | Byte | Description | Length |
michael@0 10924 * | 1 | Timer Value Tag | 1 |
michael@0 10925 * | 2 | Length = 03 | 1 |
michael@0 10926 * | 3 | Hour | 1 |
michael@0 10927 * | 4 | Minute | 1 |
michael@0 10928 * | 5 | Second | 1 |
michael@0 10929 */
michael@0 10930 retrieveTimerValue: function(length) {
michael@0 10931 let GsmPDUHelper = this.context.GsmPDUHelper;
michael@0 10932 let value = {
michael@0 10933 timerValue: (GsmPDUHelper.readSwappedNibbleBcdNum(1) * 60 * 60) +
michael@0 10934 (GsmPDUHelper.readSwappedNibbleBcdNum(1) * 60) +
michael@0 10935 (GsmPDUHelper.readSwappedNibbleBcdNum(1))
michael@0 10936 };
michael@0 10937 return value;
michael@0 10938 },
michael@0 10939
michael@0 10940 /**
michael@0 10941 * Immediate Response.
michael@0 10942 *
michael@0 10943 * | Byte | Description | Length |
michael@0 10944 * | 1 | Immediate Response Tag | 1 |
michael@0 10945 * | 2 | Length = 00 | 1 |
michael@0 10946 */
michael@0 10947 retrieveImmediaResponse: function(length) {
michael@0 10948 return {};
michael@0 10949 },
michael@0 10950
michael@0 10951 /**
michael@0 10952 * URL
michael@0 10953 *
michael@0 10954 * | Byte | Description | Length |
michael@0 10955 * | 1 | URL Tag | 1 |
michael@0 10956 * | 2 ~ (Y+1) | Length(X) | Y |
michael@0 10957 * | (Y+2) ~ | URL | X |
michael@0 10958 * | (Y+1+X) | | |
michael@0 10959 */
michael@0 10960 retrieveUrl: function(length) {
michael@0 10961 let GsmPDUHelper = this.context.GsmPDUHelper;
michael@0 10962 let s = "";
michael@0 10963 for (let i = 0; i < length; i++) {
michael@0 10964 s += String.fromCharCode(GsmPDUHelper.readHexOctet());
michael@0 10965 }
michael@0 10966 return {url: s};
michael@0 10967 },
michael@0 10968
michael@0 10969 /**
michael@0 10970 * Next Action Indicator List.
michael@0 10971 *
michael@0 10972 * | Byte | Description | Length |
michael@0 10973 * | 1 | Next Action tag | 1 |
michael@0 10974 * | 1 | Length(X) | 1 |
michael@0 10975 * | 3~ | Next Action List | X |
michael@0 10976 * | 3+X-1 | | |
michael@0 10977 */
michael@0 10978 retrieveNextActionList: function(length) {
michael@0 10979 let GsmPDUHelper = this.context.GsmPDUHelper;
michael@0 10980 let nextActionList = [];
michael@0 10981 for (let i = 0; i < length; i++) {
michael@0 10982 nextActionList.push(GsmPDUHelper.readHexOctet());
michael@0 10983 }
michael@0 10984 return nextActionList;
michael@0 10985 },
michael@0 10986
michael@0 10987 searchForTag: function(tag, ctlvs) {
michael@0 10988 let iter = Iterator(ctlvs);
michael@0 10989 return this.searchForNextTag(tag, iter);
michael@0 10990 },
michael@0 10991
michael@0 10992 searchForNextTag: function(tag, iter) {
michael@0 10993 for (let [index, ctlv] in iter) {
michael@0 10994 if ((ctlv.tag & ~COMPREHENSIONTLV_FLAG_CR) == tag) {
michael@0 10995 return ctlv;
michael@0 10996 }
michael@0 10997 }
michael@0 10998 return null;
michael@0 10999 },
michael@0 11000 };
michael@0 11001 StkProactiveCmdHelperObject.prototype[COMPREHENSIONTLV_TAG_COMMAND_DETAILS] = function COMPREHENSIONTLV_TAG_COMMAND_DETAILS(length) {
michael@0 11002 return this.retrieveCommandDetails(length);
michael@0 11003 };
michael@0 11004 StkProactiveCmdHelperObject.prototype[COMPREHENSIONTLV_TAG_DEVICE_ID] = function COMPREHENSIONTLV_TAG_DEVICE_ID(length) {
michael@0 11005 return this.retrieveDeviceId(length);
michael@0 11006 };
michael@0 11007 StkProactiveCmdHelperObject.prototype[COMPREHENSIONTLV_TAG_ALPHA_ID] = function COMPREHENSIONTLV_TAG_ALPHA_ID(length) {
michael@0 11008 return this.retrieveAlphaId(length);
michael@0 11009 };
michael@0 11010 StkProactiveCmdHelperObject.prototype[COMPREHENSIONTLV_TAG_DURATION] = function COMPREHENSIONTLV_TAG_DURATION(length) {
michael@0 11011 return this.retrieveDuration(length);
michael@0 11012 };
michael@0 11013 StkProactiveCmdHelperObject.prototype[COMPREHENSIONTLV_TAG_ADDRESS] = function COMPREHENSIONTLV_TAG_ADDRESS(length) {
michael@0 11014 return this.retrieveAddress(length);
michael@0 11015 };
michael@0 11016 StkProactiveCmdHelperObject.prototype[COMPREHENSIONTLV_TAG_TEXT_STRING] = function COMPREHENSIONTLV_TAG_TEXT_STRING(length) {
michael@0 11017 return this.retrieveTextString(length);
michael@0 11018 };
michael@0 11019 StkProactiveCmdHelperObject.prototype[COMPREHENSIONTLV_TAG_TONE] = function COMPREHENSIONTLV_TAG_TONE(length) {
michael@0 11020 return this.retrieveTone(length);
michael@0 11021 };
michael@0 11022 StkProactiveCmdHelperObject.prototype[COMPREHENSIONTLV_TAG_ITEM] = function COMPREHENSIONTLV_TAG_ITEM(length) {
michael@0 11023 return this.retrieveItem(length);
michael@0 11024 };
michael@0 11025 StkProactiveCmdHelperObject.prototype[COMPREHENSIONTLV_TAG_ITEM_ID] = function COMPREHENSIONTLV_TAG_ITEM_ID(length) {
michael@0 11026 return this.retrieveItemId(length);
michael@0 11027 };
michael@0 11028 StkProactiveCmdHelperObject.prototype[COMPREHENSIONTLV_TAG_RESPONSE_LENGTH] = function COMPREHENSIONTLV_TAG_RESPONSE_LENGTH(length) {
michael@0 11029 return this.retrieveResponseLength(length);
michael@0 11030 };
michael@0 11031 StkProactiveCmdHelperObject.prototype[COMPREHENSIONTLV_TAG_FILE_LIST] = function COMPREHENSIONTLV_TAG_FILE_LIST(length) {
michael@0 11032 return this.retrieveFileList(length);
michael@0 11033 };
michael@0 11034 StkProactiveCmdHelperObject.prototype[COMPREHENSIONTLV_TAG_DEFAULT_TEXT] = function COMPREHENSIONTLV_TAG_DEFAULT_TEXT(length) {
michael@0 11035 return this.retrieveDefaultText(length);
michael@0 11036 };
michael@0 11037 StkProactiveCmdHelperObject.prototype[COMPREHENSIONTLV_TAG_EVENT_LIST] = function COMPREHENSIONTLV_TAG_EVENT_LIST(length) {
michael@0 11038 return this.retrieveEventList(length);
michael@0 11039 };
michael@0 11040 StkProactiveCmdHelperObject.prototype[COMPREHENSIONTLV_TAG_TIMER_IDENTIFIER] = function COMPREHENSIONTLV_TAG_TIMER_IDENTIFIER(length) {
michael@0 11041 return this.retrieveTimerId(length);
michael@0 11042 };
michael@0 11043 StkProactiveCmdHelperObject.prototype[COMPREHENSIONTLV_TAG_TIMER_VALUE] = function COMPREHENSIONTLV_TAG_TIMER_VALUE(length) {
michael@0 11044 return this.retrieveTimerValue(length);
michael@0 11045 };
michael@0 11046 StkProactiveCmdHelperObject.prototype[COMPREHENSIONTLV_TAG_IMMEDIATE_RESPONSE] = function COMPREHENSIONTLV_TAG_IMMEDIATE_RESPONSE(length) {
michael@0 11047 return this.retrieveImmediaResponse(length);
michael@0 11048 };
michael@0 11049 StkProactiveCmdHelperObject.prototype[COMPREHENSIONTLV_TAG_URL] = function COMPREHENSIONTLV_TAG_URL(length) {
michael@0 11050 return this.retrieveUrl(length);
michael@0 11051 };
michael@0 11052 StkProactiveCmdHelperObject.prototype[COMPREHENSIONTLV_TAG_NEXT_ACTION_IND] = function COMPREHENSIONTLV_TAG_NEXT_ACTION_IND(length) {
michael@0 11053 return this.retrieveNextActionList(length);
michael@0 11054 };
michael@0 11055
michael@0 11056 function ComprehensionTlvHelperObject(aContext) {
michael@0 11057 this.context = aContext;
michael@0 11058 }
michael@0 11059 ComprehensionTlvHelperObject.prototype = {
michael@0 11060 context: null,
michael@0 11061
michael@0 11062 /**
michael@0 11063 * Decode raw data to a Comprehension-TLV.
michael@0 11064 */
michael@0 11065 decode: function() {
michael@0 11066 let GsmPDUHelper = this.context.GsmPDUHelper;
michael@0 11067
michael@0 11068 let hlen = 0; // For header(tag field + length field) length.
michael@0 11069 let temp = GsmPDUHelper.readHexOctet();
michael@0 11070 hlen++;
michael@0 11071
michael@0 11072 // TS 101.220, clause 7.1.1
michael@0 11073 let tag, cr;
michael@0 11074 switch (temp) {
michael@0 11075 // TS 101.220, clause 7.1.1
michael@0 11076 case 0x0: // Not used.
michael@0 11077 case 0xff: // Not used.
michael@0 11078 case 0x80: // Reserved for future use.
michael@0 11079 throw new Error("Invalid octet when parsing Comprehension TLV :" + temp);
michael@0 11080 case 0x7f: // Tag is three byte format.
michael@0 11081 // TS 101.220 clause 7.1.1.2.
michael@0 11082 // | Byte 1 | Byte 2 | Byte 3 |
michael@0 11083 // | | 8 | 7 | 6 | 5 | 4 | 3 | 2 | 1 | |
michael@0 11084 // | 0x7f |CR | Tag Value |
michael@0 11085 tag = (GsmPDUHelper.readHexOctet() << 8) | GsmPDUHelper.readHexOctet();
michael@0 11086 hlen += 2;
michael@0 11087 cr = (tag & 0x8000) !== 0;
michael@0 11088 tag &= ~0x8000;
michael@0 11089 break;
michael@0 11090 default: // Tag is single byte format.
michael@0 11091 tag = temp;
michael@0 11092 // TS 101.220 clause 7.1.1.1.
michael@0 11093 // | 8 | 7 | 6 | 5 | 4 | 3 | 2 | 1 |
michael@0 11094 // |CR | Tag Value |
michael@0 11095 cr = (tag & 0x80) !== 0;
michael@0 11096 tag &= ~0x80;
michael@0 11097 }
michael@0 11098
michael@0 11099 // TS 101.220 clause 7.1.2, Length Encoding.
michael@0 11100 // Length | Byte 1 | Byte 2 | Byte 3 | Byte 4 |
michael@0 11101 // 0 - 127 | 00 - 7f | N/A | N/A | N/A |
michael@0 11102 // 128-255 | 81 | 80 - ff| N/A | N/A |
michael@0 11103 // 256-65535| 82 | 0100 - ffff | N/A |
michael@0 11104 // 65536- | 83 | 010000 - ffffff |
michael@0 11105 // 16777215
michael@0 11106 //
michael@0 11107 // Length errors: TS 11.14, clause 6.10.6
michael@0 11108
michael@0 11109 let length; // Data length.
michael@0 11110 temp = GsmPDUHelper.readHexOctet();
michael@0 11111 hlen++;
michael@0 11112 if (temp < 0x80) {
michael@0 11113 length = temp;
michael@0 11114 } else if (temp == 0x81) {
michael@0 11115 length = GsmPDUHelper.readHexOctet();
michael@0 11116 hlen++;
michael@0 11117 if (length < 0x80) {
michael@0 11118 throw new Error("Invalid length in Comprehension TLV :" + length);
michael@0 11119 }
michael@0 11120 } else if (temp == 0x82) {
michael@0 11121 length = (GsmPDUHelper.readHexOctet() << 8) | GsmPDUHelper.readHexOctet();
michael@0 11122 hlen += 2;
michael@0 11123 if (lenth < 0x0100) {
michael@0 11124 throw new Error("Invalid length in 3-byte Comprehension TLV :" + length);
michael@0 11125 }
michael@0 11126 } else if (temp == 0x83) {
michael@0 11127 length = (GsmPDUHelper.readHexOctet() << 16) |
michael@0 11128 (GsmPDUHelper.readHexOctet() << 8) |
michael@0 11129 GsmPDUHelper.readHexOctet();
michael@0 11130 hlen += 3;
michael@0 11131 if (length < 0x010000) {
michael@0 11132 throw new Error("Invalid length in 4-byte Comprehension TLV :" + length);
michael@0 11133 }
michael@0 11134 } else {
michael@0 11135 throw new Error("Invalid octet in Comprehension TLV :" + temp);
michael@0 11136 }
michael@0 11137
michael@0 11138 let ctlv = {
michael@0 11139 tag: tag,
michael@0 11140 length: length,
michael@0 11141 value: this.context.StkProactiveCmdHelper.retrieve(tag, length),
michael@0 11142 cr: cr,
michael@0 11143 hlen: hlen
michael@0 11144 };
michael@0 11145 return ctlv;
michael@0 11146 },
michael@0 11147
michael@0 11148 decodeChunks: function(length) {
michael@0 11149 let chunks = [];
michael@0 11150 let index = 0;
michael@0 11151 while (index < length) {
michael@0 11152 let tlv = this.decode();
michael@0 11153 chunks.push(tlv);
michael@0 11154 index += tlv.length;
michael@0 11155 index += tlv.hlen;
michael@0 11156 }
michael@0 11157 return chunks;
michael@0 11158 },
michael@0 11159
michael@0 11160 /**
michael@0 11161 * Write Location Info Comprehension TLV.
michael@0 11162 *
michael@0 11163 * @param loc location Information.
michael@0 11164 */
michael@0 11165 writeLocationInfoTlv: function(loc) {
michael@0 11166 let GsmPDUHelper = this.context.GsmPDUHelper;
michael@0 11167
michael@0 11168 GsmPDUHelper.writeHexOctet(COMPREHENSIONTLV_TAG_LOCATION_INFO |
michael@0 11169 COMPREHENSIONTLV_FLAG_CR);
michael@0 11170 GsmPDUHelper.writeHexOctet(loc.gsmCellId > 0xffff ? 9 : 7);
michael@0 11171 // From TS 11.14, clause 12.19
michael@0 11172 // "The mobile country code (MCC), the mobile network code (MNC),
michael@0 11173 // the location area code (LAC) and the cell ID are
michael@0 11174 // coded as in TS 04.08."
michael@0 11175 // And from TS 04.08 and TS 24.008,
michael@0 11176 // the format is as follows:
michael@0 11177 //
michael@0 11178 // MCC = MCC_digit_1 + MCC_digit_2 + MCC_digit_3
michael@0 11179 //
michael@0 11180 // 8 7 6 5 4 3 2 1
michael@0 11181 // +-------------+-------------+
michael@0 11182 // | MCC digit 2 | MCC digit 1 | octet 2
michael@0 11183 // | MNC digit 3 | MCC digit 3 | octet 3
michael@0 11184 // | MNC digit 2 | MNC digit 1 | octet 4
michael@0 11185 // +-------------+-------------+
michael@0 11186 //
michael@0 11187 // Also in TS 24.008
michael@0 11188 // "However a network operator may decide to
michael@0 11189 // use only two digits in the MNC in the LAI over the
michael@0 11190 // radio interface. In this case, bits 5 to 8 of octet 3
michael@0 11191 // shall be coded as '1111'".
michael@0 11192
michael@0 11193 // MCC & MNC, 3 octets
michael@0 11194 let mcc = loc.mcc, mnc;
michael@0 11195 if (loc.mnc.length == 2) {
michael@0 11196 mnc = "F" + loc.mnc;
michael@0 11197 } else {
michael@0 11198 mnc = loc.mnc[2] + loc.mnc[0] + loc.mnc[1];
michael@0 11199 }
michael@0 11200 GsmPDUHelper.writeSwappedNibbleBCD(mcc + mnc);
michael@0 11201
michael@0 11202 // LAC, 2 octets
michael@0 11203 GsmPDUHelper.writeHexOctet((loc.gsmLocationAreaCode >> 8) & 0xff);
michael@0 11204 GsmPDUHelper.writeHexOctet(loc.gsmLocationAreaCode & 0xff);
michael@0 11205
michael@0 11206 // Cell Id
michael@0 11207 if (loc.gsmCellId > 0xffff) {
michael@0 11208 // UMTS/WCDMA, gsmCellId is 28 bits.
michael@0 11209 GsmPDUHelper.writeHexOctet((loc.gsmCellId >> 24) & 0xff);
michael@0 11210 GsmPDUHelper.writeHexOctet((loc.gsmCellId >> 16) & 0xff);
michael@0 11211 GsmPDUHelper.writeHexOctet((loc.gsmCellId >> 8) & 0xff);
michael@0 11212 GsmPDUHelper.writeHexOctet(loc.gsmCellId & 0xff);
michael@0 11213 } else {
michael@0 11214 // GSM, gsmCellId is 16 bits.
michael@0 11215 GsmPDUHelper.writeHexOctet((loc.gsmCellId >> 8) & 0xff);
michael@0 11216 GsmPDUHelper.writeHexOctet(loc.gsmCellId & 0xff);
michael@0 11217 }
michael@0 11218 },
michael@0 11219
michael@0 11220 /**
michael@0 11221 * Given a geckoError string, this function translates it into cause value
michael@0 11222 * and write the value into buffer.
michael@0 11223 *
michael@0 11224 * @param geckoError Error string that is passed to gecko.
michael@0 11225 */
michael@0 11226 writeCauseTlv: function(geckoError) {
michael@0 11227 let GsmPDUHelper = this.context.GsmPDUHelper;
michael@0 11228
michael@0 11229 let cause = -1;
michael@0 11230 for (let errorNo in RIL_ERROR_TO_GECKO_ERROR) {
michael@0 11231 if (geckoError == RIL_ERROR_TO_GECKO_ERROR[errorNo]) {
michael@0 11232 cause = errorNo;
michael@0 11233 break;
michael@0 11234 }
michael@0 11235 }
michael@0 11236 cause = (cause == -1) ? ERROR_SUCCESS : cause;
michael@0 11237
michael@0 11238 GsmPDUHelper.writeHexOctet(COMPREHENSIONTLV_TAG_CAUSE |
michael@0 11239 COMPREHENSIONTLV_FLAG_CR);
michael@0 11240 GsmPDUHelper.writeHexOctet(2); // For single cause value.
michael@0 11241
michael@0 11242 // TS 04.08, clause 10.5.4.11: National standard code + user location.
michael@0 11243 GsmPDUHelper.writeHexOctet(0x60);
michael@0 11244
michael@0 11245 // TS 04.08, clause 10.5.4.11: ext bit = 1 + 7 bits for cause.
michael@0 11246 // +-----------------+----------------------------------+
michael@0 11247 // | Ext = 1 (1 bit) | Cause (7 bits) |
michael@0 11248 // +-----------------+----------------------------------+
michael@0 11249 GsmPDUHelper.writeHexOctet(0x80 | cause);
michael@0 11250 },
michael@0 11251
michael@0 11252 writeDateTimeZoneTlv: function(date) {
michael@0 11253 let GsmPDUHelper = this.context.GsmPDUHelper;
michael@0 11254
michael@0 11255 GsmPDUHelper.writeHexOctet(COMPREHENSIONTLV_TAG_DATE_TIME_ZONE);
michael@0 11256 GsmPDUHelper.writeHexOctet(7);
michael@0 11257 GsmPDUHelper.writeTimestamp(date);
michael@0 11258 },
michael@0 11259
michael@0 11260 writeLanguageTlv: function(language) {
michael@0 11261 let GsmPDUHelper = this.context.GsmPDUHelper;
michael@0 11262
michael@0 11263 GsmPDUHelper.writeHexOctet(COMPREHENSIONTLV_TAG_LANGUAGE);
michael@0 11264 GsmPDUHelper.writeHexOctet(2);
michael@0 11265
michael@0 11266 // ISO 639-1, Alpha-2 code
michael@0 11267 // TS 123.038, clause 6.2.1, GSM 7 bit Default Alphabet
michael@0 11268 GsmPDUHelper.writeHexOctet(
michael@0 11269 PDU_NL_LOCKING_SHIFT_TABLES[PDU_NL_IDENTIFIER_DEFAULT].indexOf(language[0]));
michael@0 11270 GsmPDUHelper.writeHexOctet(
michael@0 11271 PDU_NL_LOCKING_SHIFT_TABLES[PDU_NL_IDENTIFIER_DEFAULT].indexOf(language[1]));
michael@0 11272 },
michael@0 11273
michael@0 11274 /**
michael@0 11275 * Write Timer Value Comprehension TLV.
michael@0 11276 *
michael@0 11277 * @param seconds length of time during of the timer.
michael@0 11278 * @param cr Comprehension Required or not
michael@0 11279 */
michael@0 11280 writeTimerValueTlv: function(seconds, cr) {
michael@0 11281 let GsmPDUHelper = this.context.GsmPDUHelper;
michael@0 11282
michael@0 11283 GsmPDUHelper.writeHexOctet(COMPREHENSIONTLV_TAG_TIMER_VALUE |
michael@0 11284 (cr ? COMPREHENSIONTLV_FLAG_CR : 0));
michael@0 11285 GsmPDUHelper.writeHexOctet(3);
michael@0 11286
michael@0 11287 // TS 102.223, clause 8.38
michael@0 11288 // +----------------+------------------+-------------------+
michael@0 11289 // | hours (1 byte) | minutes (1 btye) | secounds (1 byte) |
michael@0 11290 // +----------------+------------------+-------------------+
michael@0 11291 GsmPDUHelper.writeSwappedNibbleBCDNum(Math.floor(seconds / 60 / 60));
michael@0 11292 GsmPDUHelper.writeSwappedNibbleBCDNum(Math.floor(seconds / 60) % 60);
michael@0 11293 GsmPDUHelper.writeSwappedNibbleBCDNum(seconds % 60);
michael@0 11294 },
michael@0 11295
michael@0 11296 getSizeOfLengthOctets: function(length) {
michael@0 11297 if (length >= 0x10000) {
michael@0 11298 return 4; // 0x83, len_1, len_2, len_3
michael@0 11299 } else if (length >= 0x100) {
michael@0 11300 return 3; // 0x82, len_1, len_2
michael@0 11301 } else if (length >= 0x80) {
michael@0 11302 return 2; // 0x81, len
michael@0 11303 } else {
michael@0 11304 return 1; // len
michael@0 11305 }
michael@0 11306 },
michael@0 11307
michael@0 11308 writeLength: function(length) {
michael@0 11309 let GsmPDUHelper = this.context.GsmPDUHelper;
michael@0 11310
michael@0 11311 // TS 101.220 clause 7.1.2, Length Encoding.
michael@0 11312 // Length | Byte 1 | Byte 2 | Byte 3 | Byte 4 |
michael@0 11313 // 0 - 127 | 00 - 7f | N/A | N/A | N/A |
michael@0 11314 // 128-255 | 81 | 80 - ff| N/A | N/A |
michael@0 11315 // 256-65535| 82 | 0100 - ffff | N/A |
michael@0 11316 // 65536- | 83 | 010000 - ffffff |
michael@0 11317 // 16777215
michael@0 11318 if (length < 0x80) {
michael@0 11319 GsmPDUHelper.writeHexOctet(length);
michael@0 11320 } else if (0x80 <= length && length < 0x100) {
michael@0 11321 GsmPDUHelper.writeHexOctet(0x81);
michael@0 11322 GsmPDUHelper.writeHexOctet(length);
michael@0 11323 } else if (0x100 <= length && length < 0x10000) {
michael@0 11324 GsmPDUHelper.writeHexOctet(0x82);
michael@0 11325 GsmPDUHelper.writeHexOctet((length >> 8) & 0xff);
michael@0 11326 GsmPDUHelper.writeHexOctet(length & 0xff);
michael@0 11327 } else if (0x10000 <= length && length < 0x1000000) {
michael@0 11328 GsmPDUHelper.writeHexOctet(0x83);
michael@0 11329 GsmPDUHelper.writeHexOctet((length >> 16) & 0xff);
michael@0 11330 GsmPDUHelper.writeHexOctet((length >> 8) & 0xff);
michael@0 11331 GsmPDUHelper.writeHexOctet(length & 0xff);
michael@0 11332 } else {
michael@0 11333 throw new Error("Invalid length value :" + length);
michael@0 11334 }
michael@0 11335 },
michael@0 11336 };
michael@0 11337
michael@0 11338 function BerTlvHelperObject(aContext) {
michael@0 11339 this.context = aContext;
michael@0 11340 }
michael@0 11341 BerTlvHelperObject.prototype = {
michael@0 11342 context: null,
michael@0 11343
michael@0 11344 /**
michael@0 11345 * Decode Ber TLV.
michael@0 11346 *
michael@0 11347 * @param dataLen
michael@0 11348 * The length of data in bytes.
michael@0 11349 */
michael@0 11350 decode: function(dataLen) {
michael@0 11351 let GsmPDUHelper = this.context.GsmPDUHelper;
michael@0 11352
michael@0 11353 let hlen = 0;
michael@0 11354 let tag = GsmPDUHelper.readHexOctet();
michael@0 11355 hlen++;
michael@0 11356
michael@0 11357 // The length is coded onto 1 or 2 bytes.
michael@0 11358 // Length | Byte 1 | Byte 2
michael@0 11359 // 0 - 127 | length ('00' to '7f') | N/A
michael@0 11360 // 128 - 255 | '81' | length ('80' to 'ff')
michael@0 11361 let length;
michael@0 11362 let temp = GsmPDUHelper.readHexOctet();
michael@0 11363 hlen++;
michael@0 11364 if (temp < 0x80) {
michael@0 11365 length = temp;
michael@0 11366 } else if (temp === 0x81) {
michael@0 11367 length = GsmPDUHelper.readHexOctet();
michael@0 11368 hlen++;
michael@0 11369 if (length < 0x80) {
michael@0 11370 throw new Error("Invalid length " + length);
michael@0 11371 }
michael@0 11372 } else {
michael@0 11373 throw new Error("Invalid length octet " + temp);
michael@0 11374 }
michael@0 11375
michael@0 11376 // Header + body length check.
michael@0 11377 if (dataLen - hlen !== length) {
michael@0 11378 throw new Error("Unexpected BerTlvHelper value length!!");
michael@0 11379 }
michael@0 11380
michael@0 11381 let method = this[tag];
michael@0 11382 if (typeof method != "function") {
michael@0 11383 throw new Error("Unknown Ber tag 0x" + tag.toString(16));
michael@0 11384 }
michael@0 11385
michael@0 11386 let value = method.call(this, length);
michael@0 11387
michael@0 11388 return {
michael@0 11389 tag: tag,
michael@0 11390 length: length,
michael@0 11391 value: value
michael@0 11392 };
michael@0 11393 },
michael@0 11394
michael@0 11395 /**
michael@0 11396 * Process the value part for FCP template TLV.
michael@0 11397 *
michael@0 11398 * @param length
michael@0 11399 * The length of data in bytes.
michael@0 11400 */
michael@0 11401 processFcpTemplate: function(length) {
michael@0 11402 let tlvs = this.decodeChunks(length);
michael@0 11403 return tlvs;
michael@0 11404 },
michael@0 11405
michael@0 11406 /**
michael@0 11407 * Process the value part for proactive command TLV.
michael@0 11408 *
michael@0 11409 * @param length
michael@0 11410 * The length of data in bytes.
michael@0 11411 */
michael@0 11412 processProactiveCommand: function(length) {
michael@0 11413 let ctlvs = this.context.ComprehensionTlvHelper.decodeChunks(length);
michael@0 11414 return ctlvs;
michael@0 11415 },
michael@0 11416
michael@0 11417 /**
michael@0 11418 * Decode raw data to a Ber-TLV.
michael@0 11419 */
michael@0 11420 decodeInnerTlv: function() {
michael@0 11421 let GsmPDUHelper = this.context.GsmPDUHelper;
michael@0 11422 let tag = GsmPDUHelper.readHexOctet();
michael@0 11423 let length = GsmPDUHelper.readHexOctet();
michael@0 11424 return {
michael@0 11425 tag: tag,
michael@0 11426 length: length,
michael@0 11427 value: this.retrieve(tag, length)
michael@0 11428 };
michael@0 11429 },
michael@0 11430
michael@0 11431 decodeChunks: function(length) {
michael@0 11432 let chunks = [];
michael@0 11433 let index = 0;
michael@0 11434 while (index < length) {
michael@0 11435 let tlv = this.decodeInnerTlv();
michael@0 11436 if (tlv.value) {
michael@0 11437 chunks.push(tlv);
michael@0 11438 }
michael@0 11439 index += tlv.length;
michael@0 11440 // tag + length fields consume 2 bytes.
michael@0 11441 index += 2;
michael@0 11442 }
michael@0 11443 return chunks;
michael@0 11444 },
michael@0 11445
michael@0 11446 retrieve: function(tag, length) {
michael@0 11447 let method = this[tag];
michael@0 11448 if (typeof method != "function") {
michael@0 11449 if (DEBUG) {
michael@0 11450 this.context.debug("Unknown Ber tag : 0x" + tag.toString(16));
michael@0 11451 }
michael@0 11452 let Buf = this.context.Buf;
michael@0 11453 Buf.seekIncoming(length * Buf.PDU_HEX_OCTET_SIZE);
michael@0 11454 return null;
michael@0 11455 }
michael@0 11456 return method.call(this, length);
michael@0 11457 },
michael@0 11458
michael@0 11459 /**
michael@0 11460 * File Size Data.
michael@0 11461 *
michael@0 11462 * | Byte | Description | Length |
michael@0 11463 * | 1 | Tag | 1 |
michael@0 11464 * | 2 | Length | 1 |
michael@0 11465 * | 3 to X+24 | Number of allocated data bytes in the file | X |
michael@0 11466 * | | , excluding structural information | |
michael@0 11467 */
michael@0 11468 retrieveFileSizeData: function(length) {
michael@0 11469 let GsmPDUHelper = this.context.GsmPDUHelper;
michael@0 11470 let fileSizeData = 0;
michael@0 11471 for (let i = 0; i < length; i++) {
michael@0 11472 fileSizeData = fileSizeData << 8;
michael@0 11473 fileSizeData += GsmPDUHelper.readHexOctet();
michael@0 11474 }
michael@0 11475
michael@0 11476 return {fileSizeData: fileSizeData};
michael@0 11477 },
michael@0 11478
michael@0 11479 /**
michael@0 11480 * File Descriptor.
michael@0 11481 *
michael@0 11482 * | Byte | Description | Length |
michael@0 11483 * | 1 | Tag | 1 |
michael@0 11484 * | 2 | Length | 1 |
michael@0 11485 * | 3 | File descriptor byte | 1 |
michael@0 11486 * | 4 | Data coding byte | 1 |
michael@0 11487 * | 5 ~ 6 | Record length | 2 |
michael@0 11488 * | 7 | Number of records | 1 |
michael@0 11489 */
michael@0 11490 retrieveFileDescriptor: function(length) {
michael@0 11491 let GsmPDUHelper = this.context.GsmPDUHelper;
michael@0 11492 let fileDescriptorByte = GsmPDUHelper.readHexOctet();
michael@0 11493 let dataCodingByte = GsmPDUHelper.readHexOctet();
michael@0 11494 // See TS 102 221 Table 11.5, we only care the least 3 bits for the
michael@0 11495 // structure of file.
michael@0 11496 let fileStructure = fileDescriptorByte & 0x07;
michael@0 11497
michael@0 11498 let fileDescriptor = {
michael@0 11499 fileStructure: fileStructure
michael@0 11500 };
michael@0 11501 // byte 5 ~ 7 are mandatory for linear fixed and cyclic files, otherwise
michael@0 11502 // they are not applicable.
michael@0 11503 if (fileStructure === UICC_EF_STRUCTURE[EF_TYPE_LINEAR_FIXED] ||
michael@0 11504 fileStructure === UICC_EF_STRUCTURE[EF_TYPE_CYCLIC]) {
michael@0 11505 fileDescriptor.recordLength = (GsmPDUHelper.readHexOctet() << 8) +
michael@0 11506 GsmPDUHelper.readHexOctet();
michael@0 11507 fileDescriptor.numOfRecords = GsmPDUHelper.readHexOctet();
michael@0 11508 }
michael@0 11509
michael@0 11510 return fileDescriptor;
michael@0 11511 },
michael@0 11512
michael@0 11513 /**
michael@0 11514 * File identifier.
michael@0 11515 *
michael@0 11516 * | Byte | Description | Length |
michael@0 11517 * | 1 | Tag | 1 |
michael@0 11518 * | 2 | Length | 1 |
michael@0 11519 * | 3 ~ 4 | File identifier | 2 |
michael@0 11520 */
michael@0 11521 retrieveFileIdentifier: function(length) {
michael@0 11522 let GsmPDUHelper = this.context.GsmPDUHelper;
michael@0 11523 return {fileId : (GsmPDUHelper.readHexOctet() << 8) +
michael@0 11524 GsmPDUHelper.readHexOctet()};
michael@0 11525 },
michael@0 11526
michael@0 11527 searchForNextTag: function(tag, iter) {
michael@0 11528 for (let [index, tlv] in iter) {
michael@0 11529 if (tlv.tag === tag) {
michael@0 11530 return tlv;
michael@0 11531 }
michael@0 11532 }
michael@0 11533 return null;
michael@0 11534 }
michael@0 11535 };
michael@0 11536 BerTlvHelperObject.prototype[BER_FCP_TEMPLATE_TAG] = function BER_FCP_TEMPLATE_TAG(length) {
michael@0 11537 return this.processFcpTemplate(length);
michael@0 11538 };
michael@0 11539 BerTlvHelperObject.prototype[BER_PROACTIVE_COMMAND_TAG] = function BER_PROACTIVE_COMMAND_TAG(length) {
michael@0 11540 return this.processProactiveCommand(length);
michael@0 11541 };
michael@0 11542 BerTlvHelperObject.prototype[BER_FCP_FILE_SIZE_DATA_TAG] = function BER_FCP_FILE_SIZE_DATA_TAG(length) {
michael@0 11543 return this.retrieveFileSizeData(length);
michael@0 11544 };
michael@0 11545 BerTlvHelperObject.prototype[BER_FCP_FILE_DESCRIPTOR_TAG] = function BER_FCP_FILE_DESCRIPTOR_TAG(length) {
michael@0 11546 return this.retrieveFileDescriptor(length);
michael@0 11547 };
michael@0 11548 BerTlvHelperObject.prototype[BER_FCP_FILE_IDENTIFIER_TAG] = function BER_FCP_FILE_IDENTIFIER_TAG(length) {
michael@0 11549 return this.retrieveFileIdentifier(length);
michael@0 11550 };
michael@0 11551
michael@0 11552 /**
michael@0 11553 * ICC Helper for getting EF path.
michael@0 11554 */
michael@0 11555 function ICCFileHelperObject(aContext) {
michael@0 11556 this.context = aContext;
michael@0 11557 }
michael@0 11558 ICCFileHelperObject.prototype = {
michael@0 11559 context: null,
michael@0 11560
michael@0 11561 /**
michael@0 11562 * This function handles only EFs that are common to RUIM, SIM, USIM
michael@0 11563 * and other types of ICC cards.
michael@0 11564 */
michael@0 11565 getCommonEFPath: function(fileId) {
michael@0 11566 switch (fileId) {
michael@0 11567 case ICC_EF_ICCID:
michael@0 11568 return EF_PATH_MF_SIM;
michael@0 11569 case ICC_EF_ADN:
michael@0 11570 return EF_PATH_MF_SIM + EF_PATH_DF_TELECOM;
michael@0 11571 case ICC_EF_PBR:
michael@0 11572 return EF_PATH_MF_SIM + EF_PATH_DF_TELECOM + EF_PATH_DF_PHONEBOOK;
michael@0 11573 }
michael@0 11574 return null;
michael@0 11575 },
michael@0 11576
michael@0 11577 /**
michael@0 11578 * This function handles EFs for SIM.
michael@0 11579 */
michael@0 11580 getSimEFPath: function(fileId) {
michael@0 11581 switch (fileId) {
michael@0 11582 case ICC_EF_FDN:
michael@0 11583 case ICC_EF_MSISDN:
michael@0 11584 case ICC_EF_SMS:
michael@0 11585 return EF_PATH_MF_SIM + EF_PATH_DF_TELECOM;
michael@0 11586 case ICC_EF_AD:
michael@0 11587 case ICC_EF_MBDN:
michael@0 11588 case ICC_EF_MWIS:
michael@0 11589 case ICC_EF_PLMNsel:
michael@0 11590 case ICC_EF_SPN:
michael@0 11591 case ICC_EF_SPDI:
michael@0 11592 case ICC_EF_SST:
michael@0 11593 case ICC_EF_PHASE:
michael@0 11594 case ICC_EF_CBMI:
michael@0 11595 case ICC_EF_CBMID:
michael@0 11596 case ICC_EF_CBMIR:
michael@0 11597 case ICC_EF_OPL:
michael@0 11598 case ICC_EF_PNN:
michael@0 11599 return EF_PATH_MF_SIM + EF_PATH_DF_GSM;
michael@0 11600 default:
michael@0 11601 return null;
michael@0 11602 }
michael@0 11603 },
michael@0 11604
michael@0 11605 /**
michael@0 11606 * This function handles EFs for USIM.
michael@0 11607 */
michael@0 11608 getUSimEFPath: function(fileId) {
michael@0 11609 switch (fileId) {
michael@0 11610 case ICC_EF_AD:
michael@0 11611 case ICC_EF_FDN:
michael@0 11612 case ICC_EF_MBDN:
michael@0 11613 case ICC_EF_MWIS:
michael@0 11614 case ICC_EF_UST:
michael@0 11615 case ICC_EF_MSISDN:
michael@0 11616 case ICC_EF_SPN:
michael@0 11617 case ICC_EF_SPDI:
michael@0 11618 case ICC_EF_CBMI:
michael@0 11619 case ICC_EF_CBMID:
michael@0 11620 case ICC_EF_CBMIR:
michael@0 11621 case ICC_EF_OPL:
michael@0 11622 case ICC_EF_PNN:
michael@0 11623 case ICC_EF_SMS:
michael@0 11624 return EF_PATH_MF_SIM + EF_PATH_ADF_USIM;
michael@0 11625 default:
michael@0 11626 // The file ids in USIM phone book entries are decided by the
michael@0 11627 // card manufacturer. So if we don't match any of the cases
michael@0 11628 // above and if its a USIM return the phone book path.
michael@0 11629 return EF_PATH_MF_SIM + EF_PATH_DF_TELECOM + EF_PATH_DF_PHONEBOOK;
michael@0 11630 }
michael@0 11631 },
michael@0 11632
michael@0 11633 /**
michael@0 11634 * This function handles EFs for RUIM
michael@0 11635 */
michael@0 11636 getRuimEFPath: function(fileId) {
michael@0 11637 switch(fileId) {
michael@0 11638 case ICC_EF_CSIM_IMSI_M:
michael@0 11639 case ICC_EF_CSIM_CDMAHOME:
michael@0 11640 case ICC_EF_CSIM_CST:
michael@0 11641 case ICC_EF_CSIM_SPN:
michael@0 11642 return EF_PATH_MF_SIM + EF_PATH_DF_CDMA;
michael@0 11643 case ICC_EF_FDN:
michael@0 11644 return EF_PATH_MF_SIM + EF_PATH_DF_TELECOM;
michael@0 11645 default:
michael@0 11646 return null;
michael@0 11647 }
michael@0 11648 },
michael@0 11649
michael@0 11650 /**
michael@0 11651 * Helper function for getting the pathId for the specific ICC record
michael@0 11652 * depeding on which type of ICC card we are using.
michael@0 11653 *
michael@0 11654 * @param fileId
michael@0 11655 * File id.
michael@0 11656 * @return The pathId or null in case of an error or invalid input.
michael@0 11657 */
michael@0 11658 getEFPath: function(fileId) {
michael@0 11659 let appType = this.context.RIL.appType;
michael@0 11660 if (appType == null) {
michael@0 11661 return null;
michael@0 11662 }
michael@0 11663
michael@0 11664 let path = this.getCommonEFPath(fileId);
michael@0 11665 if (path) {
michael@0 11666 return path;
michael@0 11667 }
michael@0 11668
michael@0 11669 switch (appType) {
michael@0 11670 case CARD_APPTYPE_SIM:
michael@0 11671 return this.getSimEFPath(fileId);
michael@0 11672 case CARD_APPTYPE_USIM:
michael@0 11673 return this.getUSimEFPath(fileId);
michael@0 11674 case CARD_APPTYPE_RUIM:
michael@0 11675 return this.getRuimEFPath(fileId);
michael@0 11676 default:
michael@0 11677 return null;
michael@0 11678 }
michael@0 11679 }
michael@0 11680 };
michael@0 11681
michael@0 11682 /**
michael@0 11683 * Helper for ICC IO functionalities.
michael@0 11684 */
michael@0 11685 function ICCIOHelperObject(aContext) {
michael@0 11686 this.context = aContext;
michael@0 11687 }
michael@0 11688 ICCIOHelperObject.prototype = {
michael@0 11689 context: null,
michael@0 11690
michael@0 11691 /**
michael@0 11692 * Load EF with type 'Linear Fixed'.
michael@0 11693 *
michael@0 11694 * @param fileId
michael@0 11695 * The file to operate on, one of the ICC_EF_* constants.
michael@0 11696 * @param recordNumber [optional]
michael@0 11697 * The number of the record shall be loaded.
michael@0 11698 * @param recordSize [optional]
michael@0 11699 * The size of the record.
michael@0 11700 * @param callback [optional]
michael@0 11701 * The callback function shall be called when the record(s) is read.
michael@0 11702 * @param onerror [optional]
michael@0 11703 * The callback function shall be called when failure.
michael@0 11704 */
michael@0 11705 loadLinearFixedEF: function(options) {
michael@0 11706 let cb;
michael@0 11707 let readRecord = (function(options) {
michael@0 11708 options.command = ICC_COMMAND_READ_RECORD;
michael@0 11709 options.p1 = options.recordNumber || 1; // Record number
michael@0 11710 options.p2 = READ_RECORD_ABSOLUTE_MODE;
michael@0 11711 options.p3 = options.recordSize;
michael@0 11712 options.callback = cb || options.callback;
michael@0 11713 this.context.RIL.iccIO(options);
michael@0 11714 }).bind(this);
michael@0 11715
michael@0 11716 options.type = EF_TYPE_LINEAR_FIXED;
michael@0 11717 options.pathId = this.context.ICCFileHelper.getEFPath(options.fileId);
michael@0 11718 if (options.recordSize) {
michael@0 11719 readRecord(options);
michael@0 11720 return;
michael@0 11721 }
michael@0 11722
michael@0 11723 cb = options.callback;
michael@0 11724 options.callback = readRecord;
michael@0 11725 this.getResponse(options);
michael@0 11726 },
michael@0 11727
michael@0 11728 /**
michael@0 11729 * Load next record from current record Id.
michael@0 11730 */
michael@0 11731 loadNextRecord: function(options) {
michael@0 11732 options.p1++;
michael@0 11733 this.context.RIL.iccIO(options);
michael@0 11734 },
michael@0 11735
michael@0 11736 /**
michael@0 11737 * Update EF with type 'Linear Fixed'.
michael@0 11738 *
michael@0 11739 * @param fileId
michael@0 11740 * The file to operate on, one of the ICC_EF_* constants.
michael@0 11741 * @param recordNumber
michael@0 11742 * The number of the record shall be updated.
michael@0 11743 * @param dataWriter [optional]
michael@0 11744 * The function for writing string parameter for the ICC_COMMAND_UPDATE_RECORD.
michael@0 11745 * @param pin2 [optional]
michael@0 11746 * PIN2 is required when updating ICC_EF_FDN.
michael@0 11747 * @param callback [optional]
michael@0 11748 * The callback function shall be called when the record is updated.
michael@0 11749 * @param onerror [optional]
michael@0 11750 * The callback function shall be called when failure.
michael@0 11751 */
michael@0 11752 updateLinearFixedEF: function(options) {
michael@0 11753 if (!options.fileId || !options.recordNumber) {
michael@0 11754 throw new Error("Unexpected fileId " + options.fileId +
michael@0 11755 " or recordNumber " + options.recordNumber);
michael@0 11756 }
michael@0 11757
michael@0 11758 options.type = EF_TYPE_LINEAR_FIXED;
michael@0 11759 options.pathId = this.context.ICCFileHelper.getEFPath(options.fileId);
michael@0 11760 let cb = options.callback;
michael@0 11761 options.callback = function callback(options) {
michael@0 11762 options.callback = cb;
michael@0 11763 options.command = ICC_COMMAND_UPDATE_RECORD;
michael@0 11764 options.p1 = options.recordNumber;
michael@0 11765 options.p2 = READ_RECORD_ABSOLUTE_MODE;
michael@0 11766 options.p3 = options.recordSize;
michael@0 11767 this.context.RIL.iccIO(options);
michael@0 11768 }.bind(this);
michael@0 11769 this.getResponse(options);
michael@0 11770 },
michael@0 11771
michael@0 11772 /**
michael@0 11773 * Load EF with type 'Transparent'.
michael@0 11774 *
michael@0 11775 * @param fileId
michael@0 11776 * The file to operate on, one of the ICC_EF_* constants.
michael@0 11777 * @param callback [optional]
michael@0 11778 * The callback function shall be called when the record(s) is read.
michael@0 11779 * @param onerror [optional]
michael@0 11780 * The callback function shall be called when failure.
michael@0 11781 */
michael@0 11782 loadTransparentEF: function(options) {
michael@0 11783 options.type = EF_TYPE_TRANSPARENT;
michael@0 11784 let cb = options.callback;
michael@0 11785 options.callback = function callback(options) {
michael@0 11786 options.callback = cb;
michael@0 11787 options.command = ICC_COMMAND_READ_BINARY;
michael@0 11788 options.p3 = options.fileSize;
michael@0 11789 this.context.RIL.iccIO(options);
michael@0 11790 }.bind(this);
michael@0 11791 this.getResponse(options);
michael@0 11792 },
michael@0 11793
michael@0 11794 /**
michael@0 11795 * Use ICC_COMMAND_GET_RESPONSE to query the EF.
michael@0 11796 *
michael@0 11797 * @param fileId
michael@0 11798 * The file to operate on, one of the ICC_EF_* constants.
michael@0 11799 */
michael@0 11800 getResponse: function(options) {
michael@0 11801 options.command = ICC_COMMAND_GET_RESPONSE;
michael@0 11802 options.pathId = options.pathId ||
michael@0 11803 this.context.ICCFileHelper.getEFPath(options.fileId);
michael@0 11804 if (!options.pathId) {
michael@0 11805 throw new Error("Unknown pathId for " + options.fileId.toString(16));
michael@0 11806 }
michael@0 11807 options.p1 = 0; // For GET_RESPONSE, p1 = 0
michael@0 11808 options.p2 = 0; // For GET_RESPONSE, p2 = 0
michael@0 11809 options.p3 = GET_RESPONSE_EF_SIZE_BYTES;
michael@0 11810 this.context.RIL.iccIO(options);
michael@0 11811 },
michael@0 11812
michael@0 11813 /**
michael@0 11814 * Process ICC I/O response.
michael@0 11815 */
michael@0 11816 processICCIO: function(options) {
michael@0 11817 let func = this[options.command];
michael@0 11818 func.call(this, options);
michael@0 11819 },
michael@0 11820
michael@0 11821 /**
michael@0 11822 * Process a ICC_COMMAND_GET_RESPONSE type command for REQUEST_SIM_IO.
michael@0 11823 */
michael@0 11824 processICCIOGetResponse: function(options) {
michael@0 11825 let Buf = this.context.Buf;
michael@0 11826 let strLen = Buf.readInt32();
michael@0 11827
michael@0 11828 let peek = this.context.GsmPDUHelper.readHexOctet();
michael@0 11829 Buf.seekIncoming(-1 * Buf.PDU_HEX_OCTET_SIZE);
michael@0 11830 if (peek === BER_FCP_TEMPLATE_TAG) {
michael@0 11831 this.processUSimGetResponse(options, strLen / 2);
michael@0 11832 } else {
michael@0 11833 this.processSimGetResponse(options);
michael@0 11834 }
michael@0 11835 Buf.readStringDelimiter(strLen);
michael@0 11836
michael@0 11837 if (options.callback) {
michael@0 11838 options.callback(options);
michael@0 11839 }
michael@0 11840 },
michael@0 11841
michael@0 11842 /**
michael@0 11843 * Helper function for processing USIM get response.
michael@0 11844 */
michael@0 11845 processUSimGetResponse: function(options, octetLen) {
michael@0 11846 let BerTlvHelper = this.context.BerTlvHelper;
michael@0 11847
michael@0 11848 let berTlv = BerTlvHelper.decode(octetLen);
michael@0 11849 // See TS 102 221 Table 11.4 for the content order of getResponse.
michael@0 11850 let iter = Iterator(berTlv.value);
michael@0 11851 let tlv = BerTlvHelper.searchForNextTag(BER_FCP_FILE_DESCRIPTOR_TAG,
michael@0 11852 iter);
michael@0 11853 if (!tlv || (tlv.value.fileStructure !== UICC_EF_STRUCTURE[options.type])) {
michael@0 11854 throw new Error("Expected EF type " + UICC_EF_STRUCTURE[options.type] +
michael@0 11855 " but read " + tlv.value.fileStructure);
michael@0 11856 }
michael@0 11857
michael@0 11858 if (tlv.value.fileStructure === UICC_EF_STRUCTURE[EF_TYPE_LINEAR_FIXED] ||
michael@0 11859 tlv.value.fileStructure === UICC_EF_STRUCTURE[EF_TYPE_CYCLIC]) {
michael@0 11860 options.recordSize = tlv.value.recordLength;
michael@0 11861 options.totalRecords = tlv.value.numOfRecords;
michael@0 11862 }
michael@0 11863
michael@0 11864 tlv = BerTlvHelper.searchForNextTag(BER_FCP_FILE_IDENTIFIER_TAG, iter);
michael@0 11865 if (!tlv || (tlv.value.fileId !== options.fileId)) {
michael@0 11866 throw new Error("Expected file ID " + options.fileId.toString(16) +
michael@0 11867 " but read " + fileId.toString(16));
michael@0 11868 }
michael@0 11869
michael@0 11870 tlv = BerTlvHelper.searchForNextTag(BER_FCP_FILE_SIZE_DATA_TAG, iter);
michael@0 11871 if (!tlv) {
michael@0 11872 throw new Error("Unexpected file size data");
michael@0 11873 }
michael@0 11874 options.fileSize = tlv.value.fileSizeData;
michael@0 11875 },
michael@0 11876
michael@0 11877 /**
michael@0 11878 * Helper function for processing SIM get response.
michael@0 11879 */
michael@0 11880 processSimGetResponse: function(options) {
michael@0 11881 let Buf = this.context.Buf;
michael@0 11882 let GsmPDUHelper = this.context.GsmPDUHelper;
michael@0 11883
michael@0 11884 // The format is from TS 51.011, clause 9.2.1
michael@0 11885
michael@0 11886 // Skip RFU, data[0] data[1].
michael@0 11887 Buf.seekIncoming(2 * Buf.PDU_HEX_OCTET_SIZE);
michael@0 11888
michael@0 11889 // File size, data[2], data[3]
michael@0 11890 options.fileSize = (GsmPDUHelper.readHexOctet() << 8) |
michael@0 11891 GsmPDUHelper.readHexOctet();
michael@0 11892
michael@0 11893 // 2 bytes File id. data[4], data[5]
michael@0 11894 let fileId = (GsmPDUHelper.readHexOctet() << 8) |
michael@0 11895 GsmPDUHelper.readHexOctet();
michael@0 11896 if (fileId != options.fileId) {
michael@0 11897 throw new Error("Expected file ID " + options.fileId.toString(16) +
michael@0 11898 " but read " + fileId.toString(16));
michael@0 11899 }
michael@0 11900
michael@0 11901 // Type of file, data[6]
michael@0 11902 let fileType = GsmPDUHelper.readHexOctet();
michael@0 11903 if (fileType != TYPE_EF) {
michael@0 11904 throw new Error("Unexpected file type " + fileType);
michael@0 11905 }
michael@0 11906
michael@0 11907 // Skip 1 byte RFU, data[7],
michael@0 11908 // 3 bytes Access conditions, data[8] data[9] data[10],
michael@0 11909 // 1 byte File status, data[11],
michael@0 11910 // 1 byte Length of the following data, data[12].
michael@0 11911 Buf.seekIncoming(((RESPONSE_DATA_STRUCTURE - RESPONSE_DATA_FILE_TYPE - 1) *
michael@0 11912 Buf.PDU_HEX_OCTET_SIZE));
michael@0 11913
michael@0 11914 // Read Structure of EF, data[13]
michael@0 11915 let efType = GsmPDUHelper.readHexOctet();
michael@0 11916 if (efType != options.type) {
michael@0 11917 throw new Error("Expected EF type " + options.type + " but read " + efType);
michael@0 11918 }
michael@0 11919
michael@0 11920 // TODO: Bug 952025.
michael@0 11921 // Length of a record, data[14].
michael@0 11922 // Only available for LINEAR_FIXED and CYCLIC.
michael@0 11923 if (efType == EF_TYPE_LINEAR_FIXED || efType == EF_TYPE_CYCLIC) {
michael@0 11924 options.recordSize = GsmPDUHelper.readHexOctet();
michael@0 11925 options.totalRecords = options.fileSize / options.recordSize;
michael@0 11926 } else {
michael@0 11927 Buf.seekIncoming(1 * Buf.PDU_HEX_OCTET_SIZE);
michael@0 11928 }
michael@0 11929 },
michael@0 11930
michael@0 11931 /**
michael@0 11932 * Process a ICC_COMMAND_READ_RECORD type command for REQUEST_SIM_IO.
michael@0 11933 */
michael@0 11934 processICCIOReadRecord: function(options) {
michael@0 11935 if (options.callback) {
michael@0 11936 options.callback(options);
michael@0 11937 }
michael@0 11938 },
michael@0 11939
michael@0 11940 /**
michael@0 11941 * Process a ICC_COMMAND_READ_BINARY type command for REQUEST_SIM_IO.
michael@0 11942 */
michael@0 11943 processICCIOReadBinary: function(options) {
michael@0 11944 if (options.callback) {
michael@0 11945 options.callback(options);
michael@0 11946 }
michael@0 11947 },
michael@0 11948
michael@0 11949 /**
michael@0 11950 * Process a ICC_COMMAND_UPDATE_RECORD type command for REQUEST_SIM_IO.
michael@0 11951 */
michael@0 11952 processICCIOUpdateRecord: function(options) {
michael@0 11953 if (options.callback) {
michael@0 11954 options.callback(options);
michael@0 11955 }
michael@0 11956 },
michael@0 11957
michael@0 11958 /**
michael@0 11959 * Process ICC IO error.
michael@0 11960 */
michael@0 11961 processICCIOError: function(options) {
michael@0 11962 let requestError = RIL_ERROR_TO_GECKO_ERROR[options.rilRequestError];
michael@0 11963 if (DEBUG) {
michael@0 11964 // See GSM11.11, TS 51.011 clause 9.4, and ISO 7816-4 for the error
michael@0 11965 // description.
michael@0 11966 let errorMsg = "ICC I/O Error code " + requestError +
michael@0 11967 " EF id = " + options.fileId.toString(16) +
michael@0 11968 " command = " + options.command.toString(16);
michael@0 11969 if (options.sw1 && options.sw2) {
michael@0 11970 errorMsg += "(" + options.sw1.toString(16) +
michael@0 11971 "/" + options.sw2.toString(16) + ")";
michael@0 11972 }
michael@0 11973 this.context.debug(errorMsg);
michael@0 11974 }
michael@0 11975 if (options.onerror) {
michael@0 11976 options.onerror(requestError);
michael@0 11977 }
michael@0 11978 },
michael@0 11979 };
michael@0 11980 ICCIOHelperObject.prototype[ICC_COMMAND_SEEK] = null;
michael@0 11981 ICCIOHelperObject.prototype[ICC_COMMAND_READ_BINARY] = function ICC_COMMAND_READ_BINARY(options) {
michael@0 11982 this.processICCIOReadBinary(options);
michael@0 11983 };
michael@0 11984 ICCIOHelperObject.prototype[ICC_COMMAND_READ_RECORD] = function ICC_COMMAND_READ_RECORD(options) {
michael@0 11985 this.processICCIOReadRecord(options);
michael@0 11986 };
michael@0 11987 ICCIOHelperObject.prototype[ICC_COMMAND_GET_RESPONSE] = function ICC_COMMAND_GET_RESPONSE(options) {
michael@0 11988 this.processICCIOGetResponse(options);
michael@0 11989 };
michael@0 11990 ICCIOHelperObject.prototype[ICC_COMMAND_UPDATE_BINARY] = null;
michael@0 11991 ICCIOHelperObject.prototype[ICC_COMMAND_UPDATE_RECORD] = function ICC_COMMAND_UPDATE_RECORD(options) {
michael@0 11992 this.processICCIOUpdateRecord(options);
michael@0 11993 };
michael@0 11994
michael@0 11995 /**
michael@0 11996 * Helper for ICC records.
michael@0 11997 */
michael@0 11998 function ICCRecordHelperObject(aContext) {
michael@0 11999 this.context = aContext;
michael@0 12000 }
michael@0 12001 ICCRecordHelperObject.prototype = {
michael@0 12002 context: null,
michael@0 12003
michael@0 12004 /**
michael@0 12005 * Fetch ICC records.
michael@0 12006 */
michael@0 12007 fetchICCRecords: function() {
michael@0 12008 switch (this.context.RIL.appType) {
michael@0 12009 case CARD_APPTYPE_SIM:
michael@0 12010 case CARD_APPTYPE_USIM:
michael@0 12011 this.context.SimRecordHelper.fetchSimRecords();
michael@0 12012 break;
michael@0 12013 case CARD_APPTYPE_RUIM:
michael@0 12014 this.context.RuimRecordHelper.fetchRuimRecords();
michael@0 12015 break;
michael@0 12016 }
michael@0 12017 },
michael@0 12018
michael@0 12019 /**
michael@0 12020 * Read the ICCID.
michael@0 12021 */
michael@0 12022 readICCID: function() {
michael@0 12023 function callback() {
michael@0 12024 let Buf = this.context.Buf;
michael@0 12025 let RIL = this.context.RIL;
michael@0 12026
michael@0 12027 let strLen = Buf.readInt32();
michael@0 12028 let octetLen = strLen / 2;
michael@0 12029 RIL.iccInfo.iccid =
michael@0 12030 this.context.GsmPDUHelper.readSwappedNibbleBcdString(octetLen, true);
michael@0 12031 // Consumes the remaining buffer if any.
michael@0 12032 let unReadBuffer = this.context.Buf.getReadAvailable() -
michael@0 12033 this.context.Buf.PDU_HEX_OCTET_SIZE;
michael@0 12034 if (unReadBuffer > 0) {
michael@0 12035 this.context.Buf.seekIncoming(unReadBuffer);
michael@0 12036 }
michael@0 12037 Buf.readStringDelimiter(strLen);
michael@0 12038
michael@0 12039 if (DEBUG) this.context.debug("ICCID: " + RIL.iccInfo.iccid);
michael@0 12040 if (RIL.iccInfo.iccid) {
michael@0 12041 this.context.ICCUtilsHelper.handleICCInfoChange();
michael@0 12042 RIL.reportStkServiceIsRunning();
michael@0 12043 }
michael@0 12044 }
michael@0 12045
michael@0 12046 this.context.ICCIOHelper.loadTransparentEF({
michael@0 12047 fileId: ICC_EF_ICCID,
michael@0 12048 callback: callback.bind(this)
michael@0 12049 });
michael@0 12050 },
michael@0 12051
michael@0 12052 /**
michael@0 12053 * Read ICC ADN like EF, i.e. EF_ADN, EF_FDN.
michael@0 12054 *
michael@0 12055 * @param fileId EF id of the ADN or FDN.
michael@0 12056 * @param onsuccess Callback to be called when success.
michael@0 12057 * @param onerror Callback to be called when error.
michael@0 12058 */
michael@0 12059 readADNLike: function(fileId, onsuccess, onerror) {
michael@0 12060 let ICCIOHelper = this.context.ICCIOHelper;
michael@0 12061
michael@0 12062 function callback(options) {
michael@0 12063 let contact =
michael@0 12064 this.context.ICCPDUHelper.readAlphaIdDiallingNumber(options.recordSize);
michael@0 12065 if (contact) {
michael@0 12066 contact.recordId = options.p1;
michael@0 12067 contacts.push(contact);
michael@0 12068 }
michael@0 12069
michael@0 12070 if (options.p1 < options.totalRecords) {
michael@0 12071 ICCIOHelper.loadNextRecord(options);
michael@0 12072 } else {
michael@0 12073 if (DEBUG) {
michael@0 12074 for (let i = 0; i < contacts.length; i++) {
michael@0 12075 this.context.debug("contact [" + i + "] " +
michael@0 12076 JSON.stringify(contacts[i]));
michael@0 12077 }
michael@0 12078 }
michael@0 12079 if (onsuccess) {
michael@0 12080 onsuccess(contacts);
michael@0 12081 }
michael@0 12082 }
michael@0 12083 }
michael@0 12084
michael@0 12085 let contacts = [];
michael@0 12086 ICCIOHelper.loadLinearFixedEF({fileId: fileId,
michael@0 12087 callback: callback.bind(this),
michael@0 12088 onerror: onerror});
michael@0 12089 },
michael@0 12090
michael@0 12091 /**
michael@0 12092 * Update ICC ADN like EFs, like EF_ADN, EF_FDN.
michael@0 12093 *
michael@0 12094 * @param fileId EF id of the ADN or FDN.
michael@0 12095 * @param contact The contact will be updated. (Shall have recordId property)
michael@0 12096 * @param pin2 PIN2 is required when updating ICC_EF_FDN.
michael@0 12097 * @param onsuccess Callback to be called when success.
michael@0 12098 * @param onerror Callback to be called when error.
michael@0 12099 */
michael@0 12100 updateADNLike: function(fileId, contact, pin2, onsuccess, onerror) {
michael@0 12101 function dataWriter(recordSize) {
michael@0 12102 this.context.ICCPDUHelper.writeAlphaIdDiallingNumber(recordSize,
michael@0 12103 contact.alphaId,
michael@0 12104 contact.number);
michael@0 12105 }
michael@0 12106
michael@0 12107 function callback(options) {
michael@0 12108 if (onsuccess) {
michael@0 12109 onsuccess();
michael@0 12110 }
michael@0 12111 }
michael@0 12112
michael@0 12113 if (!contact || !contact.recordId) {
michael@0 12114 if (onerror) onerror(GECKO_ERROR_INVALID_PARAMETER);
michael@0 12115 return;
michael@0 12116 }
michael@0 12117
michael@0 12118 this.context.ICCIOHelper.updateLinearFixedEF({
michael@0 12119 fileId: fileId,
michael@0 12120 recordNumber: contact.recordId,
michael@0 12121 dataWriter: dataWriter.bind(this),
michael@0 12122 pin2: pin2,
michael@0 12123 callback: callback.bind(this),
michael@0 12124 onerror: onerror
michael@0 12125 });
michael@0 12126 },
michael@0 12127
michael@0 12128 /**
michael@0 12129 * Read USIM/RUIM Phonebook.
michael@0 12130 *
michael@0 12131 * @param onsuccess Callback to be called when success.
michael@0 12132 * @param onerror Callback to be called when error.
michael@0 12133 */
michael@0 12134 readPBR: function(onsuccess, onerror) {
michael@0 12135 let Buf = this.context.Buf;
michael@0 12136 let GsmPDUHelper = this.context.GsmPDUHelper;
michael@0 12137 let ICCIOHelper = this.context.ICCIOHelper;
michael@0 12138 let ICCUtilsHelper = this.context.ICCUtilsHelper;
michael@0 12139 let RIL = this.context.RIL;
michael@0 12140
michael@0 12141 function callback(options) {
michael@0 12142 let strLen = Buf.readInt32();
michael@0 12143 let octetLen = strLen / 2, readLen = 0;
michael@0 12144
michael@0 12145 let pbrTlvs = [];
michael@0 12146 while (readLen < octetLen) {
michael@0 12147 let tag = GsmPDUHelper.readHexOctet();
michael@0 12148 if (tag == 0xff) {
michael@0 12149 readLen++;
michael@0 12150 Buf.seekIncoming((octetLen - readLen) * Buf.PDU_HEX_OCTET_SIZE);
michael@0 12151 break;
michael@0 12152 }
michael@0 12153
michael@0 12154 let tlvLen = GsmPDUHelper.readHexOctet();
michael@0 12155 let tlvs = ICCUtilsHelper.decodeSimTlvs(tlvLen);
michael@0 12156 pbrTlvs.push({tag: tag,
michael@0 12157 length: tlvLen,
michael@0 12158 value: tlvs});
michael@0 12159
michael@0 12160 readLen += tlvLen + 2; // +2 for tag and tlvLen
michael@0 12161 }
michael@0 12162 Buf.readStringDelimiter(strLen);
michael@0 12163
michael@0 12164 if (pbrTlvs.length > 0) {
michael@0 12165 let pbr = ICCUtilsHelper.parsePbrTlvs(pbrTlvs);
michael@0 12166 // EF_ADN is mandatory if and only if DF_PHONEBOOK is present.
michael@0 12167 if (!pbr.adn) {
michael@0 12168 if (onerror) onerror("Cannot access ADN.");
michael@0 12169 return;
michael@0 12170 }
michael@0 12171 pbrs.push(pbr);
michael@0 12172 }
michael@0 12173
michael@0 12174 if (options.p1 < options.totalRecords) {
michael@0 12175 ICCIOHelper.loadNextRecord(options);
michael@0 12176 } else {
michael@0 12177 if (onsuccess) {
michael@0 12178 RIL.iccInfoPrivate.pbrs = pbrs;
michael@0 12179 onsuccess(pbrs);
michael@0 12180 }
michael@0 12181 }
michael@0 12182 }
michael@0 12183
michael@0 12184 if (RIL.iccInfoPrivate.pbrs) {
michael@0 12185 onsuccess(RIL.iccInfoPrivate.pbrs);
michael@0 12186 return;
michael@0 12187 }
michael@0 12188
michael@0 12189 let pbrs = [];
michael@0 12190 ICCIOHelper.loadLinearFixedEF({fileId : ICC_EF_PBR,
michael@0 12191 callback: callback.bind(this),
michael@0 12192 onerror: onerror});
michael@0 12193 },
michael@0 12194
michael@0 12195 /**
michael@0 12196 * Cache EF_IAP record size.
michael@0 12197 */
michael@0 12198 _iapRecordSize: null,
michael@0 12199
michael@0 12200 /**
michael@0 12201 * Read ICC EF_IAP. (Index Administration Phonebook)
michael@0 12202 *
michael@0 12203 * @see TS 131.102, clause 4.4.2.2
michael@0 12204 *
michael@0 12205 * @param fileId EF id of the IAP.
michael@0 12206 * @param recordNumber The number of the record shall be loaded.
michael@0 12207 * @param onsuccess Callback to be called when success.
michael@0 12208 * @param onerror Callback to be called when error.
michael@0 12209 */
michael@0 12210 readIAP: function(fileId, recordNumber, onsuccess, onerror) {
michael@0 12211 function callback(options) {
michael@0 12212 let Buf = this.context.Buf;
michael@0 12213 let strLen = Buf.readInt32();
michael@0 12214 let octetLen = strLen / 2;
michael@0 12215 this._iapRecordSize = options.recordSize;
michael@0 12216
michael@0 12217 let iap = this.context.GsmPDUHelper.readHexOctetArray(octetLen);
michael@0 12218 Buf.readStringDelimiter(strLen);
michael@0 12219
michael@0 12220 if (onsuccess) {
michael@0 12221 onsuccess(iap);
michael@0 12222 }
michael@0 12223 }
michael@0 12224
michael@0 12225 this.context.ICCIOHelper.loadLinearFixedEF({
michael@0 12226 fileId: fileId,
michael@0 12227 recordNumber: recordNumber,
michael@0 12228 recordSize: this._iapRecordSize,
michael@0 12229 callback: callback.bind(this),
michael@0 12230 onerror: onerror
michael@0 12231 });
michael@0 12232 },
michael@0 12233
michael@0 12234 /**
michael@0 12235 * Update USIM/RUIM Phonebook EF_IAP.
michael@0 12236 *
michael@0 12237 * @see TS 131.102, clause 4.4.2.13
michael@0 12238 *
michael@0 12239 * @param fileId EF id of the IAP.
michael@0 12240 * @param recordNumber The identifier of the record shall be updated.
michael@0 12241 * @param iap The IAP value to be written.
michael@0 12242 * @param onsuccess Callback to be called when success.
michael@0 12243 * @param onerror Callback to be called when error.
michael@0 12244 */
michael@0 12245 updateIAP: function(fileId, recordNumber, iap, onsuccess, onerror) {
michael@0 12246 let dataWriter = function dataWriter(recordSize) {
michael@0 12247 let Buf = this.context.Buf;
michael@0 12248 let GsmPDUHelper = this.context.GsmPDUHelper;
michael@0 12249
michael@0 12250 // Write String length
michael@0 12251 let strLen = recordSize * 2;
michael@0 12252 Buf.writeInt32(strLen);
michael@0 12253
michael@0 12254 for (let i = 0; i < iap.length; i++) {
michael@0 12255 GsmPDUHelper.writeHexOctet(iap[i]);
michael@0 12256 }
michael@0 12257
michael@0 12258 Buf.writeStringDelimiter(strLen);
michael@0 12259 }.bind(this);
michael@0 12260
michael@0 12261 this.context.ICCIOHelper.updateLinearFixedEF({
michael@0 12262 fileId: fileId,
michael@0 12263 recordNumber: recordNumber,
michael@0 12264 dataWriter: dataWriter,
michael@0 12265 callback: onsuccess,
michael@0 12266 onerror: onerror
michael@0 12267 });
michael@0 12268 },
michael@0 12269
michael@0 12270 /**
michael@0 12271 * Cache EF_Email record size.
michael@0 12272 */
michael@0 12273 _emailRecordSize: null,
michael@0 12274
michael@0 12275 /**
michael@0 12276 * Read USIM/RUIM Phonebook EF_EMAIL.
michael@0 12277 *
michael@0 12278 * @see TS 131.102, clause 4.4.2.13
michael@0 12279 *
michael@0 12280 * @param fileId EF id of the EMAIL.
michael@0 12281 * @param fileType The type of the EMAIL, one of the ICC_USIM_TYPE* constants.
michael@0 12282 * @param recordNumber The number of the record shall be loaded.
michael@0 12283 * @param onsuccess Callback to be called when success.
michael@0 12284 * @param onerror Callback to be called when error.
michael@0 12285 */
michael@0 12286 readEmail: function(fileId, fileType, recordNumber, onsuccess, onerror) {
michael@0 12287 function callback(options) {
michael@0 12288 let Buf = this.context.Buf;
michael@0 12289 let ICCPDUHelper = this.context.ICCPDUHelper;
michael@0 12290
michael@0 12291 let strLen = Buf.readInt32();
michael@0 12292 let octetLen = strLen / 2;
michael@0 12293 let email = null;
michael@0 12294 this._emailRecordSize = options.recordSize;
michael@0 12295
michael@0 12296 // Read contact's email
michael@0 12297 //
michael@0 12298 // | Byte | Description | Length | M/O
michael@0 12299 // | 1 ~ X | E-mail Address | X | M
michael@0 12300 // | X+1 | ADN file SFI | 1 | C
michael@0 12301 // | X+2 | ADN file Record Identifier | 1 | C
michael@0 12302 // Note: The fields marked as C above are mandatort if the file
michael@0 12303 // is not type 1 (as specified in EF_PBR)
michael@0 12304 if (fileType == ICC_USIM_TYPE1_TAG) {
michael@0 12305 email = ICCPDUHelper.read8BitUnpackedToString(octetLen);
michael@0 12306 } else {
michael@0 12307 email = ICCPDUHelper.read8BitUnpackedToString(octetLen - 2);
michael@0 12308
michael@0 12309 // Consumes the remaining buffer
michael@0 12310 Buf.seekIncoming(2 * Buf.PDU_HEX_OCTET_SIZE); // For ADN SFI and Record Identifier
michael@0 12311 }
michael@0 12312
michael@0 12313 Buf.readStringDelimiter(strLen);
michael@0 12314
michael@0 12315 if (onsuccess) {
michael@0 12316 onsuccess(email);
michael@0 12317 }
michael@0 12318 }
michael@0 12319
michael@0 12320 this.context.ICCIOHelper.loadLinearFixedEF({
michael@0 12321 fileId: fileId,
michael@0 12322 recordNumber: recordNumber,
michael@0 12323 recordSize: this._emailRecordSize,
michael@0 12324 callback: callback.bind(this),
michael@0 12325 onerror: onerror
michael@0 12326 });
michael@0 12327 },
michael@0 12328
michael@0 12329 /**
michael@0 12330 * Update USIM/RUIM Phonebook EF_EMAIL.
michael@0 12331 *
michael@0 12332 * @see TS 131.102, clause 4.4.2.13
michael@0 12333 *
michael@0 12334 * @param pbr Phonebook Reference File.
michael@0 12335 * @param recordNumber The identifier of the record shall be updated.
michael@0 12336 * @param email The value to be written.
michael@0 12337 * @param adnRecordId The record Id of ADN, only needed if the fileType of Email is TYPE2.
michael@0 12338 * @param onsuccess Callback to be called when success.
michael@0 12339 * @param onerror Callback to be called when error.
michael@0 12340 */
michael@0 12341 updateEmail: function(pbr, recordNumber, email, adnRecordId, onsuccess, onerror) {
michael@0 12342 let fileId = pbr[USIM_PBR_EMAIL].fileId;
michael@0 12343 let fileType = pbr[USIM_PBR_EMAIL].fileType;
michael@0 12344 let dataWriter = function dataWriter(recordSize) {
michael@0 12345 let Buf = this.context.Buf;
michael@0 12346 let GsmPDUHelper = this.context.GsmPDUHelper;
michael@0 12347 let ICCPDUHelper = this.context.ICCPDUHelper;
michael@0 12348
michael@0 12349 // Write String length
michael@0 12350 let strLen = recordSize * 2;
michael@0 12351 Buf.writeInt32(strLen);
michael@0 12352
michael@0 12353 if (fileType == ICC_USIM_TYPE1_TAG) {
michael@0 12354 ICCPDUHelper.writeStringTo8BitUnpacked(recordSize, email);
michael@0 12355 } else {
michael@0 12356 ICCPDUHelper.writeStringTo8BitUnpacked(recordSize - 2, email);
michael@0 12357 GsmPDUHelper.writeHexOctet(pbr.adn.sfi || 0xff);
michael@0 12358 GsmPDUHelper.writeHexOctet(adnRecordId);
michael@0 12359 }
michael@0 12360
michael@0 12361 Buf.writeStringDelimiter(strLen);
michael@0 12362 }.bind(this);
michael@0 12363
michael@0 12364 this.context.ICCIOHelper.updateLinearFixedEF({
michael@0 12365 fileId: fileId,
michael@0 12366 recordNumber: recordNumber,
michael@0 12367 dataWriter: dataWriter,
michael@0 12368 callback: onsuccess,
michael@0 12369 onerror: onerror
michael@0 12370 });
michael@0 12371 },
michael@0 12372
michael@0 12373 /**
michael@0 12374 * Cache EF_ANR record size.
michael@0 12375 */
michael@0 12376 _anrRecordSize: null,
michael@0 12377
michael@0 12378 /**
michael@0 12379 * Read USIM/RUIM Phonebook EF_ANR.
michael@0 12380 *
michael@0 12381 * @see TS 131.102, clause 4.4.2.9
michael@0 12382 *
michael@0 12383 * @param fileId EF id of the ANR.
michael@0 12384 * @param fileType One of the ICC_USIM_TYPE* constants.
michael@0 12385 * @param recordNumber The number of the record shall be loaded.
michael@0 12386 * @param onsuccess Callback to be called when success.
michael@0 12387 * @param onerror Callback to be called when error.
michael@0 12388 */
michael@0 12389 readANR: function(fileId, fileType, recordNumber, onsuccess, onerror) {
michael@0 12390 function callback(options) {
michael@0 12391 let Buf = this.context.Buf;
michael@0 12392 let strLen = Buf.readInt32();
michael@0 12393 let number = null;
michael@0 12394 this._anrRecordSize = options.recordSize;
michael@0 12395
michael@0 12396 // Skip EF_AAS Record ID.
michael@0 12397 Buf.seekIncoming(1 * Buf.PDU_HEX_OCTET_SIZE);
michael@0 12398
michael@0 12399 number = this.context.ICCPDUHelper.readNumberWithLength();
michael@0 12400
michael@0 12401 // Skip 2 unused octets, CCP and EXT1.
michael@0 12402 Buf.seekIncoming(2 * Buf.PDU_HEX_OCTET_SIZE);
michael@0 12403
michael@0 12404 // For Type 2 there are two extra octets.
michael@0 12405 if (fileType == ICC_USIM_TYPE2_TAG) {
michael@0 12406 // Skip 2 unused octets, ADN SFI and Record Identifier.
michael@0 12407 Buf.seekIncoming(2 * Buf.PDU_HEX_OCTET_SIZE);
michael@0 12408 }
michael@0 12409
michael@0 12410 Buf.readStringDelimiter(strLen);
michael@0 12411
michael@0 12412 if (onsuccess) {
michael@0 12413 onsuccess(number);
michael@0 12414 }
michael@0 12415 }
michael@0 12416
michael@0 12417 this.context.ICCIOHelper.loadLinearFixedEF({
michael@0 12418 fileId: fileId,
michael@0 12419 recordNumber: recordNumber,
michael@0 12420 recordSize: this._anrRecordSize,
michael@0 12421 callback: callback.bind(this),
michael@0 12422 onerror: onerror
michael@0 12423 });
michael@0 12424 },
michael@0 12425
michael@0 12426 /**
michael@0 12427 * Update USIM/RUIM Phonebook EF_ANR.
michael@0 12428 *
michael@0 12429 * @see TS 131.102, clause 4.4.2.9
michael@0 12430 *
michael@0 12431 * @param pbr Phonebook Reference File.
michael@0 12432 * @param recordNumber The identifier of the record shall be updated.
michael@0 12433 * @param number The value to be written.
michael@0 12434 * @param adnRecordId The record Id of ADN, only needed if the fileType of Email is TYPE2.
michael@0 12435 * @param onsuccess Callback to be called when success.
michael@0 12436 * @param onerror Callback to be called when error.
michael@0 12437 */
michael@0 12438 updateANR: function(pbr, recordNumber, number, adnRecordId, onsuccess, onerror) {
michael@0 12439 let fileId = pbr[USIM_PBR_ANR0].fileId;
michael@0 12440 let fileType = pbr[USIM_PBR_ANR0].fileType;
michael@0 12441 let dataWriter = function dataWriter(recordSize) {
michael@0 12442 let Buf = this.context.Buf;
michael@0 12443 let GsmPDUHelper = this.context.GsmPDUHelper;
michael@0 12444
michael@0 12445 // Write String length
michael@0 12446 let strLen = recordSize * 2;
michael@0 12447 Buf.writeInt32(strLen);
michael@0 12448
michael@0 12449 // EF_AAS record Id. Unused for now.
michael@0 12450 GsmPDUHelper.writeHexOctet(0xff);
michael@0 12451
michael@0 12452 this.context.ICCPDUHelper.writeNumberWithLength(number);
michael@0 12453
michael@0 12454 // Write unused octets 0xff, CCP and EXT1.
michael@0 12455 GsmPDUHelper.writeHexOctet(0xff);
michael@0 12456 GsmPDUHelper.writeHexOctet(0xff);
michael@0 12457
michael@0 12458 // For Type 2 there are two extra octets.
michael@0 12459 if (fileType == ICC_USIM_TYPE2_TAG) {
michael@0 12460 GsmPDUHelper.writeHexOctet(pbr.adn.sfi || 0xff);
michael@0 12461 GsmPDUHelper.writeHexOctet(adnRecordId);
michael@0 12462 }
michael@0 12463
michael@0 12464 Buf.writeStringDelimiter(strLen);
michael@0 12465 }.bind(this);
michael@0 12466
michael@0 12467 this.context.ICCIOHelper.updateLinearFixedEF({
michael@0 12468 fileId: fileId,
michael@0 12469 recordNumber: recordNumber,
michael@0 12470 dataWriter: dataWriter,
michael@0 12471 callback: onsuccess,
michael@0 12472 onerror: onerror
michael@0 12473 });
michael@0 12474 },
michael@0 12475
michael@0 12476 /**
michael@0 12477 * Find free record id.
michael@0 12478 *
michael@0 12479 * @param fileId EF id.
michael@0 12480 * @param onsuccess Callback to be called when success.
michael@0 12481 * @param onerror Callback to be called when error.
michael@0 12482 */
michael@0 12483 findFreeRecordId: function(fileId, onsuccess, onerror) {
michael@0 12484 let ICCIOHelper = this.context.ICCIOHelper;
michael@0 12485
michael@0 12486 function callback(options) {
michael@0 12487 let Buf = this.context.Buf;
michael@0 12488 let GsmPDUHelper = this.context.GsmPDUHelper;
michael@0 12489
michael@0 12490 let strLen = Buf.readInt32();
michael@0 12491 let octetLen = strLen / 2;
michael@0 12492 let readLen = 0;
michael@0 12493
michael@0 12494 while (readLen < octetLen) {
michael@0 12495 let octet = GsmPDUHelper.readHexOctet();
michael@0 12496 readLen++;
michael@0 12497 if (octet != 0xff) {
michael@0 12498 break;
michael@0 12499 }
michael@0 12500 }
michael@0 12501
michael@0 12502 if (readLen == octetLen) {
michael@0 12503 // Find free record.
michael@0 12504 if (onsuccess) {
michael@0 12505 onsuccess(options.p1);
michael@0 12506 }
michael@0 12507 return;
michael@0 12508 } else {
michael@0 12509 Buf.seekIncoming((octetLen - readLen) * Buf.PDU_HEX_OCTET_SIZE);
michael@0 12510 }
michael@0 12511
michael@0 12512 Buf.readStringDelimiter(strLen);
michael@0 12513
michael@0 12514 if (options.p1 < options.totalRecords) {
michael@0 12515 ICCIOHelper.loadNextRecord(options);
michael@0 12516 } else {
michael@0 12517 // No free record found.
michael@0 12518 if (DEBUG) {
michael@0 12519 this.context.debug(CONTACT_ERR_NO_FREE_RECORD_FOUND);
michael@0 12520 }
michael@0 12521 onerror(CONTACT_ERR_NO_FREE_RECORD_FOUND);
michael@0 12522 }
michael@0 12523 }
michael@0 12524
michael@0 12525 ICCIOHelper.loadLinearFixedEF({fileId: fileId,
michael@0 12526 callback: callback.bind(this),
michael@0 12527 onerror: onerror});
michael@0 12528 },
michael@0 12529 };
michael@0 12530
michael@0 12531 /**
michael@0 12532 * Helper for (U)SIM Records.
michael@0 12533 */
michael@0 12534 function SimRecordHelperObject(aContext) {
michael@0 12535 this.context = aContext;
michael@0 12536 }
michael@0 12537 SimRecordHelperObject.prototype = {
michael@0 12538 context: null,
michael@0 12539
michael@0 12540 /**
michael@0 12541 * Fetch (U)SIM records.
michael@0 12542 */
michael@0 12543 fetchSimRecords: function() {
michael@0 12544 this.context.RIL.getIMSI();
michael@0 12545 this.readAD();
michael@0 12546 this.readSST();
michael@0 12547 },
michael@0 12548
michael@0 12549 /**
michael@0 12550 * Read EF_phase.
michael@0 12551 * This EF is only available in SIM.
michael@0 12552 */
michael@0 12553 readSimPhase: function() {
michael@0 12554 function callback() {
michael@0 12555 let Buf = this.context.Buf;
michael@0 12556 let strLen = Buf.readInt32();
michael@0 12557
michael@0 12558 let GsmPDUHelper = this.context.GsmPDUHelper;
michael@0 12559 let phase = GsmPDUHelper.readHexOctet();
michael@0 12560 // If EF_phase is coded '03' or greater, an ME supporting STK shall
michael@0 12561 // perform the PROFILE DOWNLOAD procedure.
michael@0 12562 if (RILQUIRKS_SEND_STK_PROFILE_DOWNLOAD &&
michael@0 12563 phase >= ICC_PHASE_2_PROFILE_DOWNLOAD_REQUIRED) {
michael@0 12564 this.context.RIL.sendStkTerminalProfile(STK_SUPPORTED_TERMINAL_PROFILE);
michael@0 12565 }
michael@0 12566
michael@0 12567 Buf.readStringDelimiter(strLen);
michael@0 12568 }
michael@0 12569
michael@0 12570 this.context.ICCIOHelper.loadTransparentEF({
michael@0 12571 fileId: ICC_EF_PHASE,
michael@0 12572 callback: callback.bind(this)
michael@0 12573 });
michael@0 12574 },
michael@0 12575
michael@0 12576 /**
michael@0 12577 * Read the MSISDN from the (U)SIM.
michael@0 12578 */
michael@0 12579 readMSISDN: function() {
michael@0 12580 function callback(options) {
michael@0 12581 let RIL = this.context.RIL;
michael@0 12582
michael@0 12583 let contact =
michael@0 12584 this.context.ICCPDUHelper.readAlphaIdDiallingNumber(options.recordSize);
michael@0 12585 if (!contact ||
michael@0 12586 (RIL.iccInfo.msisdn !== undefined &&
michael@0 12587 RIL.iccInfo.msisdn === contact.number)) {
michael@0 12588 return;
michael@0 12589 }
michael@0 12590 RIL.iccInfo.msisdn = contact.number;
michael@0 12591 if (DEBUG) this.context.debug("MSISDN: " + RIL.iccInfo.msisdn);
michael@0 12592 this.context.ICCUtilsHelper.handleICCInfoChange();
michael@0 12593 }
michael@0 12594
michael@0 12595 this.context.ICCIOHelper.loadLinearFixedEF({
michael@0 12596 fileId: ICC_EF_MSISDN,
michael@0 12597 callback: callback.bind(this)
michael@0 12598 });
michael@0 12599 },
michael@0 12600
michael@0 12601 /**
michael@0 12602 * Read the AD (Administrative Data) from the (U)SIM.
michael@0 12603 */
michael@0 12604 readAD: function() {
michael@0 12605 function callback() {
michael@0 12606 let Buf = this.context.Buf;
michael@0 12607 let strLen = Buf.readInt32();
michael@0 12608 // Each octet is encoded into two chars.
michael@0 12609 let octetLen = strLen / 2;
michael@0 12610 let ad = this.context.GsmPDUHelper.readHexOctetArray(octetLen);
michael@0 12611 Buf.readStringDelimiter(strLen);
michael@0 12612
michael@0 12613 if (DEBUG) {
michael@0 12614 let str = "";
michael@0 12615 for (let i = 0; i < ad.length; i++) {
michael@0 12616 str += ad[i] + ", ";
michael@0 12617 }
michael@0 12618 this.context.debug("AD: " + str);
michael@0 12619 }
michael@0 12620
michael@0 12621 let ICCUtilsHelper = this.context.ICCUtilsHelper;
michael@0 12622 let RIL = this.context.RIL;
michael@0 12623 // The 4th byte of the response is the length of MNC.
michael@0 12624 let mccMnc = ICCUtilsHelper.parseMccMncFromImsi(RIL.iccInfoPrivate.imsi,
michael@0 12625 ad && ad[3]);
michael@0 12626 if (mccMnc) {
michael@0 12627 RIL.iccInfo.mcc = mccMnc.mcc;
michael@0 12628 RIL.iccInfo.mnc = mccMnc.mnc;
michael@0 12629 ICCUtilsHelper.handleICCInfoChange();
michael@0 12630 }
michael@0 12631 }
michael@0 12632
michael@0 12633 this.context.ICCIOHelper.loadTransparentEF({
michael@0 12634 fileId: ICC_EF_AD,
michael@0 12635 callback: callback.bind(this)
michael@0 12636 });
michael@0 12637 },
michael@0 12638
michael@0 12639 /**
michael@0 12640 * Read the SPN (Service Provider Name) from the (U)SIM.
michael@0 12641 */
michael@0 12642 readSPN: function() {
michael@0 12643 function callback() {
michael@0 12644 let Buf = this.context.Buf;
michael@0 12645 let strLen = Buf.readInt32();
michael@0 12646 // Each octet is encoded into two chars.
michael@0 12647 let octetLen = strLen / 2;
michael@0 12648 let spnDisplayCondition = this.context.GsmPDUHelper.readHexOctet();
michael@0 12649 // Minus 1 because the first octet is used to store display condition.
michael@0 12650 let spn = this.context.ICCPDUHelper.readAlphaIdentifier(octetLen - 1);
michael@0 12651 Buf.readStringDelimiter(strLen);
michael@0 12652
michael@0 12653 if (DEBUG) {
michael@0 12654 this.context.debug("SPN: spn = " + spn +
michael@0 12655 ", spnDisplayCondition = " + spnDisplayCondition);
michael@0 12656 }
michael@0 12657
michael@0 12658 let RIL = this.context.RIL;
michael@0 12659 RIL.iccInfoPrivate.spnDisplayCondition = spnDisplayCondition;
michael@0 12660 RIL.iccInfo.spn = spn;
michael@0 12661 let ICCUtilsHelper = this.context.ICCUtilsHelper;
michael@0 12662 ICCUtilsHelper.updateDisplayCondition();
michael@0 12663 ICCUtilsHelper.handleICCInfoChange();
michael@0 12664 }
michael@0 12665
michael@0 12666 this.context.ICCIOHelper.loadTransparentEF({
michael@0 12667 fileId: ICC_EF_SPN,
michael@0 12668 callback: callback.bind(this)
michael@0 12669 });
michael@0 12670 },
michael@0 12671
michael@0 12672 /**
michael@0 12673 * Read the (U)SIM Service Table from the (U)SIM.
michael@0 12674 */
michael@0 12675 readSST: function() {
michael@0 12676 function callback() {
michael@0 12677 let Buf = this.context.Buf;
michael@0 12678 let RIL = this.context.RIL;
michael@0 12679
michael@0 12680 let strLen = Buf.readInt32();
michael@0 12681 // Each octet is encoded into two chars.
michael@0 12682 let octetLen = strLen / 2;
michael@0 12683 let sst = this.context.GsmPDUHelper.readHexOctetArray(octetLen);
michael@0 12684 Buf.readStringDelimiter(strLen);
michael@0 12685 RIL.iccInfoPrivate.sst = sst;
michael@0 12686 if (DEBUG) {
michael@0 12687 let str = "";
michael@0 12688 for (let i = 0; i < sst.length; i++) {
michael@0 12689 str += sst[i] + ", ";
michael@0 12690 }
michael@0 12691 this.context.debug("SST: " + str);
michael@0 12692 }
michael@0 12693
michael@0 12694 let ICCUtilsHelper = this.context.ICCUtilsHelper;
michael@0 12695 if (ICCUtilsHelper.isICCServiceAvailable("MSISDN")) {
michael@0 12696 if (DEBUG) this.context.debug("MSISDN: MSISDN is available");
michael@0 12697 this.readMSISDN();
michael@0 12698 } else {
michael@0 12699 if (DEBUG) this.context.debug("MSISDN: MSISDN service is not available");
michael@0 12700 }
michael@0 12701
michael@0 12702 // Fetch SPN and PLMN list, if some of them are available.
michael@0 12703 if (ICCUtilsHelper.isICCServiceAvailable("SPN")) {
michael@0 12704 if (DEBUG) this.context.debug("SPN: SPN is available");
michael@0 12705 this.readSPN();
michael@0 12706 } else {
michael@0 12707 if (DEBUG) this.context.debug("SPN: SPN service is not available");
michael@0 12708 }
michael@0 12709
michael@0 12710 if (ICCUtilsHelper.isICCServiceAvailable("MDN")) {
michael@0 12711 if (DEBUG) this.context.debug("MDN: MDN available.");
michael@0 12712 this.readMBDN();
michael@0 12713 } else {
michael@0 12714 if (DEBUG) this.context.debug("MDN: MDN service is not available");
michael@0 12715 }
michael@0 12716
michael@0 12717 if (ICCUtilsHelper.isICCServiceAvailable("MWIS")) {
michael@0 12718 if (DEBUG) this.context.debug("MWIS: MWIS is available");
michael@0 12719 this.readMWIS();
michael@0 12720 } else {
michael@0 12721 if (DEBUG) this.context.debug("MWIS: MWIS is not available");
michael@0 12722 }
michael@0 12723
michael@0 12724 if (ICCUtilsHelper.isICCServiceAvailable("SPDI")) {
michael@0 12725 if (DEBUG) this.context.debug("SPDI: SPDI available.");
michael@0 12726 this.readSPDI();
michael@0 12727 } else {
michael@0 12728 if (DEBUG) this.context.debug("SPDI: SPDI service is not available");
michael@0 12729 }
michael@0 12730
michael@0 12731 if (ICCUtilsHelper.isICCServiceAvailable("PNN")) {
michael@0 12732 if (DEBUG) this.context.debug("PNN: PNN is available");
michael@0 12733 this.readPNN();
michael@0 12734 } else {
michael@0 12735 if (DEBUG) this.context.debug("PNN: PNN is not available");
michael@0 12736 }
michael@0 12737
michael@0 12738 if (ICCUtilsHelper.isICCServiceAvailable("OPL")) {
michael@0 12739 if (DEBUG) this.context.debug("OPL: OPL is available");
michael@0 12740 this.readOPL();
michael@0 12741 } else {
michael@0 12742 if (DEBUG) this.context.debug("OPL: OPL is not available");
michael@0 12743 }
michael@0 12744
michael@0 12745 if (ICCUtilsHelper.isICCServiceAvailable("CBMI")) {
michael@0 12746 this.readCBMI();
michael@0 12747 } else {
michael@0 12748 RIL.cellBroadcastConfigs.CBMI = null;
michael@0 12749 }
michael@0 12750 if (ICCUtilsHelper.isICCServiceAvailable("DATA_DOWNLOAD_SMS_CB")) {
michael@0 12751 this.readCBMID();
michael@0 12752 } else {
michael@0 12753 RIL.cellBroadcastConfigs.CBMID = null;
michael@0 12754 }
michael@0 12755 if (ICCUtilsHelper.isICCServiceAvailable("CBMIR")) {
michael@0 12756 this.readCBMIR();
michael@0 12757 } else {
michael@0 12758 RIL.cellBroadcastConfigs.CBMIR = null;
michael@0 12759 }
michael@0 12760 RIL._mergeAllCellBroadcastConfigs();
michael@0 12761 }
michael@0 12762
michael@0 12763 // ICC_EF_UST has the same value with ICC_EF_SST.
michael@0 12764 this.context.ICCIOHelper.loadTransparentEF({
michael@0 12765 fileId: ICC_EF_SST,
michael@0 12766 callback: callback.bind(this)
michael@0 12767 });
michael@0 12768 },
michael@0 12769
michael@0 12770 /**
michael@0 12771 * Read (U)SIM MBDN. (Mailbox Dialling Number)
michael@0 12772 *
michael@0 12773 * @see TS 131.102, clause 4.2.60
michael@0 12774 */
michael@0 12775 readMBDN: function() {
michael@0 12776 function callback(options) {
michael@0 12777 let RIL = this.context.RIL;
michael@0 12778 let contact =
michael@0 12779 this.context.ICCPDUHelper.readAlphaIdDiallingNumber(options.recordSize);
michael@0 12780 if (!contact ||
michael@0 12781 (RIL.iccInfoPrivate.mbdn !== undefined &&
michael@0 12782 RIL.iccInfoPrivate.mbdn === contact.number)) {
michael@0 12783 return;
michael@0 12784 }
michael@0 12785 RIL.iccInfoPrivate.mbdn = contact.number;
michael@0 12786 if (DEBUG) {
michael@0 12787 this.context.debug("MBDN, alphaId=" + contact.alphaId +
michael@0 12788 " number=" + contact.number);
michael@0 12789 }
michael@0 12790 contact.rilMessageType = "iccmbdn";
michael@0 12791 RIL.sendChromeMessage(contact);
michael@0 12792 }
michael@0 12793
michael@0 12794 this.context.ICCIOHelper.loadLinearFixedEF({
michael@0 12795 fileId: ICC_EF_MBDN,
michael@0 12796 callback: callback.bind(this)
michael@0 12797 });
michael@0 12798 },
michael@0 12799
michael@0 12800 /**
michael@0 12801 * Read ICC MWIS. (Message Waiting Indication Status)
michael@0 12802 *
michael@0 12803 * @see TS 31.102, clause 4.2.63 for USIM and TS 51.011, clause 10.3.45 for SIM.
michael@0 12804 */
michael@0 12805 readMWIS: function() {
michael@0 12806 function callback(options) {
michael@0 12807 let Buf = this.context.Buf;
michael@0 12808 let RIL = this.context.RIL;
michael@0 12809
michael@0 12810 let strLen = Buf.readInt32();
michael@0 12811 // Each octet is encoded into two chars.
michael@0 12812 let octetLen = strLen / 2;
michael@0 12813 let mwis = this.context.GsmPDUHelper.readHexOctetArray(octetLen);
michael@0 12814 Buf.readStringDelimiter(strLen);
michael@0 12815 if (!mwis) {
michael@0 12816 return;
michael@0 12817 }
michael@0 12818 RIL.iccInfoPrivate.mwis = mwis; //Keep raw MWIS for updateMWIS()
michael@0 12819
michael@0 12820 let mwi = {};
michael@0 12821 // b8 b7 B6 b5 b4 b3 b2 b1 4.2.63, TS 31.102 version 11.6.0
michael@0 12822 // | | | | | | | |__ Voicemail
michael@0 12823 // | | | | | | |_____ Fax
michael@0 12824 // | | | | | |________ Electronic Mail
michael@0 12825 // | | | | |___________ Other
michael@0 12826 // | | | |______________ Videomail
michael@0 12827 // |__|__|_________________ RFU
michael@0 12828 mwi.active = ((mwis[0] & 0x01) != 0);
michael@0 12829
michael@0 12830 if (mwi.active) {
michael@0 12831 // In TS 23.040 msgCount is in the range from 0 to 255.
michael@0 12832 // The value 255 shall be taken to mean 255 or greater.
michael@0 12833 //
michael@0 12834 // However, There is no definition about 0 when MWI is active.
michael@0 12835 //
michael@0 12836 // Normally, when mwi is active, the msgCount must be larger than 0.
michael@0 12837 // Refer to other reference phone,
michael@0 12838 // 0 is usually treated as UNKNOWN for storing 2nd level MWI status (DCS).
michael@0 12839 mwi.msgCount = (mwis[1] === 0) ? GECKO_VOICEMAIL_MESSAGE_COUNT_UNKNOWN
michael@0 12840 : mwis[1];
michael@0 12841 } else {
michael@0 12842 mwi.msgCount = 0;
michael@0 12843 }
michael@0 12844
michael@0 12845 RIL.sendChromeMessage({ rilMessageType: "iccmwis",
michael@0 12846 mwi: mwi });
michael@0 12847 }
michael@0 12848
michael@0 12849 this.context.ICCIOHelper.loadLinearFixedEF({
michael@0 12850 fileId: ICC_EF_MWIS,
michael@0 12851 recordNumber: 1, // Get 1st Subscriber Profile.
michael@0 12852 callback: callback.bind(this)
michael@0 12853 });
michael@0 12854 },
michael@0 12855
michael@0 12856 /**
michael@0 12857 * Update ICC MWIS. (Message Waiting Indication Status)
michael@0 12858 *
michael@0 12859 * @see TS 31.102, clause 4.2.63 for USIM and TS 51.011, clause 10.3.45 for SIM.
michael@0 12860 */
michael@0 12861 updateMWIS: function(mwi) {
michael@0 12862 let RIL = this.context.RIL;
michael@0 12863 if (!RIL.iccInfoPrivate.mwis) {
michael@0 12864 return;
michael@0 12865 }
michael@0 12866
michael@0 12867 function dataWriter(recordSize) {
michael@0 12868 let mwis = RIL.iccInfoPrivate.mwis;
michael@0 12869
michael@0 12870 let msgCount =
michael@0 12871 (mwi.msgCount === GECKO_VOICEMAIL_MESSAGE_COUNT_UNKNOWN) ? 0 : mwi.msgCount;
michael@0 12872
michael@0 12873 [mwis[0], mwis[1]] = (mwi.active) ? [(mwis[0] | 0x01), msgCount]
michael@0 12874 : [(mwis[0] & 0xFE), 0];
michael@0 12875
michael@0 12876 let strLen = recordSize * 2;
michael@0 12877 let Buf = this.context.Buf;
michael@0 12878 Buf.writeInt32(strLen);
michael@0 12879
michael@0 12880 let GsmPDUHelper = this.context.GsmPDUHelper;
michael@0 12881 for (let i = 0; i < mwis.length; i++) {
michael@0 12882 GsmPDUHelper.writeHexOctet(mwis[i]);
michael@0 12883 }
michael@0 12884
michael@0 12885 Buf.writeStringDelimiter(strLen);
michael@0 12886 }
michael@0 12887
michael@0 12888 this.context.ICCIOHelper.updateLinearFixedEF({
michael@0 12889 fileId: ICC_EF_MWIS,
michael@0 12890 recordNumber: 1, // Update 1st Subscriber Profile.
michael@0 12891 dataWriter: dataWriter.bind(this)
michael@0 12892 });
michael@0 12893 },
michael@0 12894
michael@0 12895 /**
michael@0 12896 * Read the SPDI (Service Provider Display Information) from the (U)SIM.
michael@0 12897 *
michael@0 12898 * See TS 131.102 section 4.2.66 for USIM and TS 51.011 section 10.3.50
michael@0 12899 * for SIM.
michael@0 12900 */
michael@0 12901 readSPDI: function() {
michael@0 12902 function callback() {
michael@0 12903 let Buf = this.context.Buf;
michael@0 12904 let strLen = Buf.readInt32();
michael@0 12905 let octetLen = strLen / 2;
michael@0 12906 let readLen = 0;
michael@0 12907 let endLoop = false;
michael@0 12908
michael@0 12909 let RIL = this.context.RIL;
michael@0 12910 RIL.iccInfoPrivate.SPDI = null;
michael@0 12911
michael@0 12912 let GsmPDUHelper = this.context.GsmPDUHelper;
michael@0 12913 while ((readLen < octetLen) && !endLoop) {
michael@0 12914 let tlvTag = GsmPDUHelper.readHexOctet();
michael@0 12915 let tlvLen = GsmPDUHelper.readHexOctet();
michael@0 12916 readLen += 2; // For tag and length fields.
michael@0 12917 switch (tlvTag) {
michael@0 12918 case SPDI_TAG_SPDI:
michael@0 12919 // The value part itself is a TLV.
michael@0 12920 continue;
michael@0 12921 case SPDI_TAG_PLMN_LIST:
michael@0 12922 // This PLMN list is what we want.
michael@0 12923 RIL.iccInfoPrivate.SPDI = this.readPLMNEntries(tlvLen / 3);
michael@0 12924 readLen += tlvLen;
michael@0 12925 endLoop = true;
michael@0 12926 break;
michael@0 12927 default:
michael@0 12928 // We don't care about its content if its tag is not SPDI nor
michael@0 12929 // PLMN_LIST.
michael@0 12930 endLoop = true;
michael@0 12931 break;
michael@0 12932 }
michael@0 12933 }
michael@0 12934
michael@0 12935 // Consume unread octets.
michael@0 12936 Buf.seekIncoming((octetLen - readLen) * Buf.PDU_HEX_OCTET_SIZE);
michael@0 12937 Buf.readStringDelimiter(strLen);
michael@0 12938
michael@0 12939 if (DEBUG) {
michael@0 12940 this.context.debug("SPDI: " + JSON.stringify(RIL.iccInfoPrivate.SPDI));
michael@0 12941 }
michael@0 12942 let ICCUtilsHelper = this.context.ICCUtilsHelper;
michael@0 12943 if (ICCUtilsHelper.updateDisplayCondition()) {
michael@0 12944 ICCUtilsHelper.handleICCInfoChange();
michael@0 12945 }
michael@0 12946 }
michael@0 12947
michael@0 12948 // PLMN List is Servive 51 in USIM, EF_SPDI
michael@0 12949 this.context.ICCIOHelper.loadTransparentEF({
michael@0 12950 fileId: ICC_EF_SPDI,
michael@0 12951 callback: callback.bind(this)
michael@0 12952 });
michael@0 12953 },
michael@0 12954
michael@0 12955 _readCbmiHelper: function(which) {
michael@0 12956 let RIL = this.context.RIL;
michael@0 12957
michael@0 12958 function callback() {
michael@0 12959 let Buf = this.context.Buf;
michael@0 12960 let strLength = Buf.readInt32();
michael@0 12961
michael@0 12962 // Each Message Identifier takes two octets and each octet is encoded
michael@0 12963 // into two chars.
michael@0 12964 let numIds = strLength / 4, list = null;
michael@0 12965 if (numIds) {
michael@0 12966 list = [];
michael@0 12967 let GsmPDUHelper = this.context.GsmPDUHelper;
michael@0 12968 for (let i = 0, id; i < numIds; i++) {
michael@0 12969 id = GsmPDUHelper.readHexOctet() << 8 | GsmPDUHelper.readHexOctet();
michael@0 12970 // `Unused entries shall be set to 'FF FF'.`
michael@0 12971 if (id != 0xFFFF) {
michael@0 12972 list.push(id);
michael@0 12973 list.push(id + 1);
michael@0 12974 }
michael@0 12975 }
michael@0 12976 }
michael@0 12977 if (DEBUG) {
michael@0 12978 this.context.debug(which + ": " + JSON.stringify(list));
michael@0 12979 }
michael@0 12980
michael@0 12981 Buf.readStringDelimiter(strLength);
michael@0 12982
michael@0 12983 RIL.cellBroadcastConfigs[which] = list;
michael@0 12984 RIL._mergeAllCellBroadcastConfigs();
michael@0 12985 }
michael@0 12986
michael@0 12987 function onerror() {
michael@0 12988 RIL.cellBroadcastConfigs[which] = null;
michael@0 12989 RIL._mergeAllCellBroadcastConfigs();
michael@0 12990 }
michael@0 12991
michael@0 12992 let fileId = GLOBAL["ICC_EF_" + which];
michael@0 12993 this.context.ICCIOHelper.loadTransparentEF({
michael@0 12994 fileId: fileId,
michael@0 12995 callback: callback.bind(this),
michael@0 12996 onerror: onerror.bind(this)
michael@0 12997 });
michael@0 12998 },
michael@0 12999
michael@0 13000 /**
michael@0 13001 * Read EFcbmi (Cell Broadcast Message Identifier selection)
michael@0 13002 *
michael@0 13003 * @see 3GPP TS 31.102 v110.02.0 section 4.2.14 EFcbmi
michael@0 13004 * @see 3GPP TS 51.011 v5.0.0 section 10.3.13 EFcbmi
michael@0 13005 */
michael@0 13006 readCBMI: function() {
michael@0 13007 this._readCbmiHelper("CBMI");
michael@0 13008 },
michael@0 13009
michael@0 13010 /**
michael@0 13011 * Read EFcbmid (Cell Broadcast Message Identifier for Data Download)
michael@0 13012 *
michael@0 13013 * @see 3GPP TS 31.102 v110.02.0 section 4.2.20 EFcbmid
michael@0 13014 * @see 3GPP TS 51.011 v5.0.0 section 10.3.26 EFcbmid
michael@0 13015 */
michael@0 13016 readCBMID: function() {
michael@0 13017 this._readCbmiHelper("CBMID");
michael@0 13018 },
michael@0 13019
michael@0 13020 /**
michael@0 13021 * Read EFcbmir (Cell Broadcast Message Identifier Range selection)
michael@0 13022 *
michael@0 13023 * @see 3GPP TS 31.102 v110.02.0 section 4.2.22 EFcbmir
michael@0 13024 * @see 3GPP TS 51.011 v5.0.0 section 10.3.28 EFcbmir
michael@0 13025 */
michael@0 13026 readCBMIR: function() {
michael@0 13027 let RIL = this.context.RIL;
michael@0 13028
michael@0 13029 function callback() {
michael@0 13030 let Buf = this.context.Buf;
michael@0 13031 let strLength = Buf.readInt32();
michael@0 13032
michael@0 13033 // Each Message Identifier range takes four octets and each octet is
michael@0 13034 // encoded into two chars.
michael@0 13035 let numIds = strLength / 8, list = null;
michael@0 13036 if (numIds) {
michael@0 13037 list = [];
michael@0 13038 let GsmPDUHelper = this.context.GsmPDUHelper;
michael@0 13039 for (let i = 0, from, to; i < numIds; i++) {
michael@0 13040 // `Bytes one and two of each range identifier equal the lower value
michael@0 13041 // of a cell broadcast range, bytes three and four equal the upper
michael@0 13042 // value of a cell broadcast range.`
michael@0 13043 from = GsmPDUHelper.readHexOctet() << 8 | GsmPDUHelper.readHexOctet();
michael@0 13044 to = GsmPDUHelper.readHexOctet() << 8 | GsmPDUHelper.readHexOctet();
michael@0 13045 // `Unused entries shall be set to 'FF FF'.`
michael@0 13046 if ((from != 0xFFFF) && (to != 0xFFFF)) {
michael@0 13047 list.push(from);
michael@0 13048 list.push(to + 1);
michael@0 13049 }
michael@0 13050 }
michael@0 13051 }
michael@0 13052 if (DEBUG) {
michael@0 13053 this.context.debug("CBMIR: " + JSON.stringify(list));
michael@0 13054 }
michael@0 13055
michael@0 13056 Buf.readStringDelimiter(strLength);
michael@0 13057
michael@0 13058 RIL.cellBroadcastConfigs.CBMIR = list;
michael@0 13059 RIL._mergeAllCellBroadcastConfigs();
michael@0 13060 }
michael@0 13061
michael@0 13062 function onerror() {
michael@0 13063 RIL.cellBroadcastConfigs.CBMIR = null;
michael@0 13064 RIL._mergeAllCellBroadcastConfigs();
michael@0 13065 }
michael@0 13066
michael@0 13067 this.context.ICCIOHelper.loadTransparentEF({
michael@0 13068 fileId: ICC_EF_CBMIR,
michael@0 13069 callback: callback.bind(this),
michael@0 13070 onerror: onerror.bind(this)
michael@0 13071 });
michael@0 13072 },
michael@0 13073
michael@0 13074 /**
michael@0 13075 * Read OPL (Operator PLMN List) from (U)SIM.
michael@0 13076 *
michael@0 13077 * See 3GPP TS 31.102 Sec. 4.2.59 for USIM
michael@0 13078 * 3GPP TS 51.011 Sec. 10.3.42 for SIM.
michael@0 13079 */
michael@0 13080 readOPL: function() {
michael@0 13081 let ICCIOHelper = this.context.ICCIOHelper;
michael@0 13082 let opl = [];
michael@0 13083 function callback(options) {
michael@0 13084 let Buf = this.context.Buf;
michael@0 13085 let GsmPDUHelper = this.context.GsmPDUHelper;
michael@0 13086
michael@0 13087 let strLen = Buf.readInt32();
michael@0 13088 // The first 7 bytes are LAI (for UMTS) and the format of LAI is defined
michael@0 13089 // in 3GPP TS 23.003, Sec 4.1
michael@0 13090 // +-------------+---------+
michael@0 13091 // | Octet 1 - 3 | MCC/MNC |
michael@0 13092 // +-------------+---------+
michael@0 13093 // | Octet 4 - 7 | LAC |
michael@0 13094 // +-------------+---------+
michael@0 13095 let mccMnc = [GsmPDUHelper.readHexOctet(),
michael@0 13096 GsmPDUHelper.readHexOctet(),
michael@0 13097 GsmPDUHelper.readHexOctet()];
michael@0 13098 if (mccMnc[0] != 0xFF || mccMnc[1] != 0xFF || mccMnc[2] != 0xFF) {
michael@0 13099 let oplElement = {};
michael@0 13100 let semiOctets = [];
michael@0 13101 for (let i = 0; i < mccMnc.length; i++) {
michael@0 13102 semiOctets.push((mccMnc[i] & 0xf0) >> 4);
michael@0 13103 semiOctets.push(mccMnc[i] & 0x0f);
michael@0 13104 }
michael@0 13105 let reformat = [semiOctets[1], semiOctets[0], semiOctets[3],
michael@0 13106 semiOctets[5], semiOctets[4], semiOctets[2]];
michael@0 13107 let buf = "";
michael@0 13108 for (let i = 0; i < reformat.length; i++) {
michael@0 13109 if (reformat[i] != 0xF) {
michael@0 13110 buf += GsmPDUHelper.semiOctetToBcdChar(reformat[i]);
michael@0 13111 }
michael@0 13112 if (i === 2) {
michael@0 13113 // 0-2: MCC
michael@0 13114 oplElement.mcc = buf;
michael@0 13115 buf = "";
michael@0 13116 } else if (i === 5) {
michael@0 13117 // 3-5: MNC
michael@0 13118 oplElement.mnc = buf;
michael@0 13119 }
michael@0 13120 }
michael@0 13121 // LAC/TAC
michael@0 13122 oplElement.lacTacStart =
michael@0 13123 (GsmPDUHelper.readHexOctet() << 8) | GsmPDUHelper.readHexOctet();
michael@0 13124 oplElement.lacTacEnd =
michael@0 13125 (GsmPDUHelper.readHexOctet() << 8) | GsmPDUHelper.readHexOctet();
michael@0 13126 // PLMN Network Name Record Identifier
michael@0 13127 oplElement.pnnRecordId = GsmPDUHelper.readHexOctet();
michael@0 13128 if (DEBUG) {
michael@0 13129 this.context.debug("OPL: [" + (opl.length + 1) + "]: " +
michael@0 13130 JSON.stringify(oplElement));
michael@0 13131 }
michael@0 13132 opl.push(oplElement);
michael@0 13133 } else {
michael@0 13134 Buf.seekIncoming(5 * Buf.PDU_HEX_OCTET_SIZE);
michael@0 13135 }
michael@0 13136 Buf.readStringDelimiter(strLen);
michael@0 13137
michael@0 13138 if (options.p1 < options.totalRecords) {
michael@0 13139 ICCIOHelper.loadNextRecord(options);
michael@0 13140 } else {
michael@0 13141 this.context.RIL.iccInfoPrivate.OPL = opl;
michael@0 13142 }
michael@0 13143 }
michael@0 13144
michael@0 13145 ICCIOHelper.loadLinearFixedEF({fileId: ICC_EF_OPL,
michael@0 13146 callback: callback.bind(this)});
michael@0 13147 },
michael@0 13148
michael@0 13149 /**
michael@0 13150 * Read PNN (PLMN Network Name) from (U)SIM.
michael@0 13151 *
michael@0 13152 * See 3GPP TS 31.102 Sec. 4.2.58 for USIM
michael@0 13153 * 3GPP TS 51.011 Sec. 10.3.41 for SIM.
michael@0 13154 */
michael@0 13155 readPNN: function() {
michael@0 13156 let ICCIOHelper = this.context.ICCIOHelper;
michael@0 13157 function callback(options) {
michael@0 13158 let pnnElement;
michael@0 13159 let Buf = this.context.Buf;
michael@0 13160 let strLen = Buf.readInt32();
michael@0 13161 let octetLen = strLen / 2;
michael@0 13162 let readLen = 0;
michael@0 13163
michael@0 13164 let GsmPDUHelper = this.context.GsmPDUHelper;
michael@0 13165 while (readLen < octetLen) {
michael@0 13166 let tlvTag = GsmPDUHelper.readHexOctet();
michael@0 13167
michael@0 13168 if (tlvTag == 0xFF) {
michael@0 13169 // Unused byte
michael@0 13170 readLen++;
michael@0 13171 Buf.seekIncoming((octetLen - readLen) * Buf.PDU_HEX_OCTET_SIZE);
michael@0 13172 break;
michael@0 13173 }
michael@0 13174
michael@0 13175 // Needs this check to avoid initializing twice.
michael@0 13176 pnnElement = pnnElement || {};
michael@0 13177
michael@0 13178 let tlvLen = GsmPDUHelper.readHexOctet();
michael@0 13179
michael@0 13180 switch (tlvTag) {
michael@0 13181 case PNN_IEI_FULL_NETWORK_NAME:
michael@0 13182 pnnElement.fullName = GsmPDUHelper.readNetworkName(tlvLen);
michael@0 13183 break;
michael@0 13184 case PNN_IEI_SHORT_NETWORK_NAME:
michael@0 13185 pnnElement.shortName = GsmPDUHelper.readNetworkName(tlvLen);
michael@0 13186 break;
michael@0 13187 default:
michael@0 13188 Buf.seekIncoming(tlvLen * Buf.PDU_HEX_OCTET_SIZE);
michael@0 13189 break;
michael@0 13190 }
michael@0 13191
michael@0 13192 readLen += (tlvLen + 2); // +2 for tlvTag and tlvLen
michael@0 13193 }
michael@0 13194 Buf.readStringDelimiter(strLen);
michael@0 13195
michael@0 13196 if (pnnElement) {
michael@0 13197 pnn.push(pnnElement);
michael@0 13198 }
michael@0 13199
michael@0 13200 // Will ignore remaining records when got the contents of a record are all 0xff.
michael@0 13201 if (pnnElement && options.p1 < options.totalRecords) {
michael@0 13202 ICCIOHelper.loadNextRecord(options);
michael@0 13203 } else {
michael@0 13204 if (DEBUG) {
michael@0 13205 for (let i = 0; i < pnn.length; i++) {
michael@0 13206 this.context.debug("PNN: [" + i + "]: " + JSON.stringify(pnn[i]));
michael@0 13207 }
michael@0 13208 }
michael@0 13209 this.context.RIL.iccInfoPrivate.PNN = pnn;
michael@0 13210 }
michael@0 13211 }
michael@0 13212
michael@0 13213 let pnn = [];
michael@0 13214 ICCIOHelper.loadLinearFixedEF({fileId: ICC_EF_PNN,
michael@0 13215 callback: callback.bind(this)});
michael@0 13216 },
michael@0 13217
michael@0 13218 /**
michael@0 13219 * Read the list of PLMN (Public Land Mobile Network) entries
michael@0 13220 * We cannot directly rely on readSwappedNibbleBcdToString(),
michael@0 13221 * since it will no correctly handle some corner-cases that are
michael@0 13222 * not a problem in our case (0xFF 0xFF 0xFF).
michael@0 13223 *
michael@0 13224 * @param length The number of PLMN records.
michael@0 13225 * @return An array of string corresponding to the PLMNs.
michael@0 13226 */
michael@0 13227 readPLMNEntries: function(length) {
michael@0 13228 let plmnList = [];
michael@0 13229 // Each PLMN entry has 3 bytes.
michael@0 13230 if (DEBUG) {
michael@0 13231 this.context.debug("PLMN entries length = " + length);
michael@0 13232 }
michael@0 13233 let GsmPDUHelper = this.context.GsmPDUHelper;
michael@0 13234 let index = 0;
michael@0 13235 while (index < length) {
michael@0 13236 // Unused entries will be 0xFFFFFF, according to EF_SPDI
michael@0 13237 // specs (TS 131 102, section 4.2.66)
michael@0 13238 try {
michael@0 13239 let plmn = [GsmPDUHelper.readHexOctet(),
michael@0 13240 GsmPDUHelper.readHexOctet(),
michael@0 13241 GsmPDUHelper.readHexOctet()];
michael@0 13242 if (DEBUG) {
michael@0 13243 this.context.debug("Reading PLMN entry: [" + index + "]: '" + plmn + "'");
michael@0 13244 }
michael@0 13245 if (plmn[0] != 0xFF &&
michael@0 13246 plmn[1] != 0xFF &&
michael@0 13247 plmn[2] != 0xFF) {
michael@0 13248 let semiOctets = [];
michael@0 13249 for (let idx = 0; idx < plmn.length; idx++) {
michael@0 13250 semiOctets.push((plmn[idx] & 0xF0) >> 4);
michael@0 13251 semiOctets.push(plmn[idx] & 0x0F);
michael@0 13252 }
michael@0 13253
michael@0 13254 // According to TS 24.301, 9.9.3.12, the semi octets is arranged
michael@0 13255 // in format:
michael@0 13256 // Byte 1: MCC[2] | MCC[1]
michael@0 13257 // Byte 2: MNC[3] | MCC[3]
michael@0 13258 // Byte 3: MNC[2] | MNC[1]
michael@0 13259 // Therefore, we need to rearrange them.
michael@0 13260 let reformat = [semiOctets[1], semiOctets[0], semiOctets[3],
michael@0 13261 semiOctets[5], semiOctets[4], semiOctets[2]];
michael@0 13262 let buf = "";
michael@0 13263 let plmnEntry = {};
michael@0 13264 for (let i = 0; i < reformat.length; i++) {
michael@0 13265 if (reformat[i] != 0xF) {
michael@0 13266 buf += GsmPDUHelper.semiOctetToBcdChar(reformat[i]);
michael@0 13267 }
michael@0 13268 if (i === 2) {
michael@0 13269 // 0-2: MCC
michael@0 13270 plmnEntry.mcc = buf;
michael@0 13271 buf = "";
michael@0 13272 } else if (i === 5) {
michael@0 13273 // 3-5: MNC
michael@0 13274 plmnEntry.mnc = buf;
michael@0 13275 }
michael@0 13276 }
michael@0 13277 if (DEBUG) {
michael@0 13278 this.context.debug("PLMN = " + plmnEntry.mcc + ", " + plmnEntry.mnc);
michael@0 13279 }
michael@0 13280 plmnList.push(plmnEntry);
michael@0 13281 }
michael@0 13282 } catch (e) {
michael@0 13283 if (DEBUG) {
michael@0 13284 this.context.debug("PLMN entry " + index + " is invalid.");
michael@0 13285 }
michael@0 13286 break;
michael@0 13287 }
michael@0 13288 index ++;
michael@0 13289 }
michael@0 13290 return plmnList;
michael@0 13291 },
michael@0 13292
michael@0 13293 /**
michael@0 13294 * Read the SMS from the ICC.
michael@0 13295 *
michael@0 13296 * @param recordNumber The number of the record shall be loaded.
michael@0 13297 * @param onsuccess Callback to be called when success.
michael@0 13298 * @param onerror Callback to be called when error.
michael@0 13299 */
michael@0 13300 readSMS: function(recordNumber, onsuccess, onerror) {
michael@0 13301 function callback(options) {
michael@0 13302 let Buf = this.context.Buf;
michael@0 13303 let strLen = Buf.readInt32();
michael@0 13304
michael@0 13305 // TS 51.011, 10.5.3 EF_SMS
michael@0 13306 // b3 b2 b1
michael@0 13307 // 0 0 1 message received by MS from network; message read
michael@0 13308 // 0 1 1 message received by MS from network; message to be read
michael@0 13309 // 1 1 1 MS originating message; message to be sent
michael@0 13310 // 1 0 1 MS originating message; message sent to the network:
michael@0 13311 let GsmPDUHelper = this.context.GsmPDUHelper;
michael@0 13312 let status = GsmPDUHelper.readHexOctet();
michael@0 13313
michael@0 13314 let message = GsmPDUHelper.readMessage();
michael@0 13315 message.simStatus = status;
michael@0 13316
michael@0 13317 // Consumes the remaining buffer
michael@0 13318 Buf.seekIncoming(Buf.getReadAvailable() - Buf.PDU_HEX_OCTET_SIZE);
michael@0 13319
michael@0 13320 Buf.readStringDelimiter(strLen);
michael@0 13321
michael@0 13322 if (message) {
michael@0 13323 onsuccess(message);
michael@0 13324 } else {
michael@0 13325 onerror("Failed to decode SMS on SIM #" + recordNumber);
michael@0 13326 }
michael@0 13327 }
michael@0 13328
michael@0 13329 this.context.ICCIOHelper.loadLinearFixedEF({
michael@0 13330 fileId: ICC_EF_SMS,
michael@0 13331 recordNumber: recordNumber,
michael@0 13332 callback: callback.bind(this),
michael@0 13333 onerror: onerror
michael@0 13334 });
michael@0 13335 },
michael@0 13336 };
michael@0 13337
michael@0 13338 function RuimRecordHelperObject(aContext) {
michael@0 13339 this.context = aContext;
michael@0 13340 }
michael@0 13341 RuimRecordHelperObject.prototype = {
michael@0 13342 context: null,
michael@0 13343
michael@0 13344 fetchRuimRecords: function() {
michael@0 13345 this.getIMSI_M();
michael@0 13346 this.readCST();
michael@0 13347 this.readCDMAHome();
michael@0 13348 this.context.RIL.getCdmaSubscription();
michael@0 13349 },
michael@0 13350
michael@0 13351 /**
michael@0 13352 * Get IMSI_M from CSIM/RUIM.
michael@0 13353 * See 3GPP2 C.S0065 Sec. 5.2.2
michael@0 13354 */
michael@0 13355 getIMSI_M: function() {
michael@0 13356 function callback() {
michael@0 13357 let Buf = this.context.Buf;
michael@0 13358 let strLen = Buf.readInt32();
michael@0 13359 let encodedImsi = this.context.GsmPDUHelper.readHexOctetArray(strLen / 2);
michael@0 13360 Buf.readStringDelimiter(strLen);
michael@0 13361
michael@0 13362 if ((encodedImsi[CSIM_IMSI_M_PROGRAMMED_BYTE] & 0x80)) { // IMSI_M programmed
michael@0 13363 let RIL = this.context.RIL;
michael@0 13364 RIL.iccInfoPrivate.imsi = this.decodeIMSI(encodedImsi);
michael@0 13365 RIL.sendChromeMessage({rilMessageType: "iccimsi",
michael@0 13366 imsi: RIL.iccInfoPrivate.imsi});
michael@0 13367
michael@0 13368 let ICCUtilsHelper = this.context.ICCUtilsHelper;
michael@0 13369 let mccMnc = ICCUtilsHelper.parseMccMncFromImsi(RIL.iccInfoPrivate.imsi);
michael@0 13370 if (mccMnc) {
michael@0 13371 RIL.iccInfo.mcc = mccMnc.mcc;
michael@0 13372 RIL.iccInfo.mnc = mccMnc.mnc;
michael@0 13373 ICCUtilsHelper.handleICCInfoChange();
michael@0 13374 }
michael@0 13375 }
michael@0 13376 }
michael@0 13377
michael@0 13378 this.context.ICCIOHelper.loadTransparentEF({
michael@0 13379 fileId: ICC_EF_CSIM_IMSI_M,
michael@0 13380 callback: callback.bind(this)
michael@0 13381 });
michael@0 13382 },
michael@0 13383
michael@0 13384 /**
michael@0 13385 * Decode IMSI from IMSI_M
michael@0 13386 * See 3GPP2 C.S0005 Sec. 2.3.1
michael@0 13387 * +---+---------+------------+---+--------+---------+---+---------+--------+
michael@0 13388 * |RFU| MCC | programmed |RFU| MNC | MIN1 |RFU| MIN2 | CLASS |
michael@0 13389 * +---+---------+------------+---+--------+---------+---+---------+--------+
michael@0 13390 * | 6 | 10 bits | 8 bits | 1 | 7 bits | 24 bits | 6 | 10 bits | 8 bits |
michael@0 13391 * +---+---------+------------+---+--------+---------+---+---------+--------+
michael@0 13392 */
michael@0 13393 decodeIMSI: function(encodedImsi) {
michael@0 13394 // MCC: 10 bits, 3 digits
michael@0 13395 let encodedMCC = ((encodedImsi[CSIM_IMSI_M_MCC_BYTE + 1] & 0x03) << 8) +
michael@0 13396 (encodedImsi[CSIM_IMSI_M_MCC_BYTE] & 0xff);
michael@0 13397 let mcc = this.decodeIMSIValue(encodedMCC, 3);
michael@0 13398
michael@0 13399 // MNC: 7 bits, 2 digits
michael@0 13400 let encodedMNC = encodedImsi[CSIM_IMSI_M_MNC_BYTE] & 0x7f;
michael@0 13401 let mnc = this.decodeIMSIValue(encodedMNC, 2);
michael@0 13402
michael@0 13403 // MIN2: 10 bits, 3 digits
michael@0 13404 let encodedMIN2 = ((encodedImsi[CSIM_IMSI_M_MIN2_BYTE + 1] & 0x03) << 8) +
michael@0 13405 (encodedImsi[CSIM_IMSI_M_MIN2_BYTE] & 0xff);
michael@0 13406 let min2 = this.decodeIMSIValue(encodedMIN2, 3);
michael@0 13407
michael@0 13408 // MIN1: 10+4+10 bits, 3+1+3 digits
michael@0 13409 let encodedMIN1First3 = ((encodedImsi[CSIM_IMSI_M_MIN1_BYTE + 2] & 0xff) << 2) +
michael@0 13410 ((encodedImsi[CSIM_IMSI_M_MIN1_BYTE + 1] & 0xc0) >> 6);
michael@0 13411 let min1First3 = this.decodeIMSIValue(encodedMIN1First3, 3);
michael@0 13412
michael@0 13413 let encodedFourthDigit = (encodedImsi[CSIM_IMSI_M_MIN1_BYTE + 1] & 0x3c) >> 2;
michael@0 13414 if (encodedFourthDigit > 9) {
michael@0 13415 encodedFourthDigit = 0;
michael@0 13416 }
michael@0 13417 let fourthDigit = encodedFourthDigit.toString();
michael@0 13418
michael@0 13419 let encodedMIN1Last3 = ((encodedImsi[CSIM_IMSI_M_MIN1_BYTE + 1] & 0x03) << 8) +
michael@0 13420 (encodedImsi[CSIM_IMSI_M_MIN1_BYTE] & 0xff);
michael@0 13421 let min1Last3 = this.decodeIMSIValue(encodedMIN1Last3, 3);
michael@0 13422
michael@0 13423 return mcc + mnc + min2 + min1First3 + fourthDigit + min1Last3;
michael@0 13424 },
michael@0 13425
michael@0 13426 /**
michael@0 13427 * Decode IMSI Helper function
michael@0 13428 * See 3GPP2 C.S0005 section 2.3.1.1
michael@0 13429 */
michael@0 13430 decodeIMSIValue: function(encoded, length) {
michael@0 13431 let offset = length === 3 ? 111 : 11;
michael@0 13432 let value = encoded + offset;
michael@0 13433
michael@0 13434 for (let base = 10, temp = value, i = 0; i < length; i++) {
michael@0 13435 if (temp % 10 === 0) {
michael@0 13436 value -= base;
michael@0 13437 }
michael@0 13438 temp = Math.floor(value / base);
michael@0 13439 base = base * 10;
michael@0 13440 }
michael@0 13441
michael@0 13442 let s = value.toString();
michael@0 13443 while (s.length < length) {
michael@0 13444 s = "0" + s;
michael@0 13445 }
michael@0 13446
michael@0 13447 return s;
michael@0 13448 },
michael@0 13449
michael@0 13450 /**
michael@0 13451 * Read CDMAHOME for CSIM.
michael@0 13452 * See 3GPP2 C.S0023 Sec. 3.4.8.
michael@0 13453 */
michael@0 13454 readCDMAHome: function() {
michael@0 13455 let ICCIOHelper = this.context.ICCIOHelper;
michael@0 13456
michael@0 13457 function callback(options) {
michael@0 13458 let Buf = this.context.Buf;
michael@0 13459 let GsmPDUHelper = this.context.GsmPDUHelper;
michael@0 13460
michael@0 13461 let strLen = Buf.readInt32();
michael@0 13462 let tempOctet = GsmPDUHelper.readHexOctet();
michael@0 13463 cdmaHomeSystemId.push(((GsmPDUHelper.readHexOctet() & 0x7f) << 8) | tempOctet);
michael@0 13464 tempOctet = GsmPDUHelper.readHexOctet();
michael@0 13465 cdmaHomeNetworkId.push(((GsmPDUHelper.readHexOctet() & 0xff) << 8) | tempOctet);
michael@0 13466
michael@0 13467 // Consuming the last octet: band class.
michael@0 13468 Buf.seekIncoming(Buf.PDU_HEX_OCTET_SIZE);
michael@0 13469
michael@0 13470 Buf.readStringDelimiter(strLen);
michael@0 13471 if (options.p1 < options.totalRecords) {
michael@0 13472 ICCIOHelper.loadNextRecord(options);
michael@0 13473 } else {
michael@0 13474 if (DEBUG) {
michael@0 13475 this.context.debug("CDMAHome system id: " +
michael@0 13476 JSON.stringify(cdmaHomeSystemId));
michael@0 13477 this.context.debug("CDMAHome network id: " +
michael@0 13478 JSON.stringify(cdmaHomeNetworkId));
michael@0 13479 }
michael@0 13480 this.context.RIL.cdmaHome = {
michael@0 13481 systemId: cdmaHomeSystemId,
michael@0 13482 networkId: cdmaHomeNetworkId
michael@0 13483 };
michael@0 13484 }
michael@0 13485 }
michael@0 13486
michael@0 13487 let cdmaHomeSystemId = [], cdmaHomeNetworkId = [];
michael@0 13488 ICCIOHelper.loadLinearFixedEF({fileId: ICC_EF_CSIM_CDMAHOME,
michael@0 13489 callback: callback.bind(this)});
michael@0 13490 },
michael@0 13491
michael@0 13492 /**
michael@0 13493 * Read CDMA Service Table.
michael@0 13494 * See 3GPP2 C.S0023 Sec. 3.4.18
michael@0 13495 */
michael@0 13496 readCST: function() {
michael@0 13497 function callback() {
michael@0 13498 let Buf = this.context.Buf;
michael@0 13499 let RIL = this.context.RIL;
michael@0 13500
michael@0 13501 let strLen = Buf.readInt32();
michael@0 13502 // Each octet is encoded into two chars.
michael@0 13503 RIL.iccInfoPrivate.cst =
michael@0 13504 this.context.GsmPDUHelper.readHexOctetArray(strLen / 2);
michael@0 13505 Buf.readStringDelimiter(strLen);
michael@0 13506
michael@0 13507 if (DEBUG) {
michael@0 13508 let str = "";
michael@0 13509 for (let i = 0; i < RIL.iccInfoPrivate.cst.length; i++) {
michael@0 13510 str += RIL.iccInfoPrivate.cst[i] + ", ";
michael@0 13511 }
michael@0 13512 this.context.debug("CST: " + str);
michael@0 13513 }
michael@0 13514
michael@0 13515 if (this.context.ICCUtilsHelper.isICCServiceAvailable("SPN")) {
michael@0 13516 if (DEBUG) this.context.debug("SPN: SPN is available");
michael@0 13517 this.readSPN();
michael@0 13518 }
michael@0 13519 }
michael@0 13520 this.context.ICCIOHelper.loadTransparentEF({
michael@0 13521 fileId: ICC_EF_CSIM_CST,
michael@0 13522 callback: callback.bind(this)
michael@0 13523 });
michael@0 13524 },
michael@0 13525
michael@0 13526 readSPN: function() {
michael@0 13527 function callback() {
michael@0 13528 let Buf = this.context.Buf;
michael@0 13529 let strLen = Buf.readInt32();
michael@0 13530 let octetLen = strLen / 2;
michael@0 13531
michael@0 13532 let GsmPDUHelper = this.context.GsmPDUHelper;
michael@0 13533 let displayCondition = GsmPDUHelper.readHexOctet();
michael@0 13534 let codingScheme = GsmPDUHelper.readHexOctet();
michael@0 13535 // Skip one octet: language indicator.
michael@0 13536 Buf.seekIncoming(Buf.PDU_HEX_OCTET_SIZE);
michael@0 13537 let readLen = 3;
michael@0 13538
michael@0 13539 // SPN String ends up with 0xff.
michael@0 13540 let userDataBuffer = [];
michael@0 13541
michael@0 13542 while (readLen < octetLen) {
michael@0 13543 let octet = GsmPDUHelper.readHexOctet();
michael@0 13544 readLen++;
michael@0 13545 if (octet == 0xff) {
michael@0 13546 break;
michael@0 13547 }
michael@0 13548 userDataBuffer.push(octet);
michael@0 13549 }
michael@0 13550
michael@0 13551 this.context.BitBufferHelper.startRead(userDataBuffer);
michael@0 13552
michael@0 13553 let CdmaPDUHelper = this.context.CdmaPDUHelper;
michael@0 13554 let msgLen;
michael@0 13555 switch (CdmaPDUHelper.getCdmaMsgEncoding(codingScheme)) {
michael@0 13556 case PDU_DCS_MSG_CODING_7BITS_ALPHABET:
michael@0 13557 msgLen = Math.floor(userDataBuffer.length * 8 / 7);
michael@0 13558 break;
michael@0 13559 case PDU_DCS_MSG_CODING_8BITS_ALPHABET:
michael@0 13560 msgLen = userDataBuffer.length;
michael@0 13561 break;
michael@0 13562 case PDU_DCS_MSG_CODING_16BITS_ALPHABET:
michael@0 13563 msgLen = Math.floor(userDataBuffer.length / 2);
michael@0 13564 break;
michael@0 13565 }
michael@0 13566
michael@0 13567 let RIL = this.context.RIL;
michael@0 13568 RIL.iccInfo.spn = CdmaPDUHelper.decodeCdmaPDUMsg(codingScheme, null, msgLen);
michael@0 13569 if (DEBUG) {
michael@0 13570 this.context.debug("CDMA SPN: " + RIL.iccInfo.spn +
michael@0 13571 ", Display condition: " + displayCondition);
michael@0 13572 }
michael@0 13573 RIL.iccInfoPrivate.spnDisplayCondition = displayCondition;
michael@0 13574 Buf.seekIncoming((octetLen - readLen) * Buf.PDU_HEX_OCTET_SIZE);
michael@0 13575 Buf.readStringDelimiter(strLen);
michael@0 13576 }
michael@0 13577
michael@0 13578 this.context.ICCIOHelper.loadTransparentEF({
michael@0 13579 fileId: ICC_EF_CSIM_SPN,
michael@0 13580 callback: callback.bind(this)
michael@0 13581 });
michael@0 13582 }
michael@0 13583 };
michael@0 13584
michael@0 13585 /**
michael@0 13586 * Helper functions for ICC utilities.
michael@0 13587 */
michael@0 13588 function ICCUtilsHelperObject(aContext) {
michael@0 13589 this.context = aContext;
michael@0 13590 }
michael@0 13591 ICCUtilsHelperObject.prototype = {
michael@0 13592 context: null,
michael@0 13593
michael@0 13594 /**
michael@0 13595 * Get network names by using EF_OPL and EF_PNN
michael@0 13596 *
michael@0 13597 * @See 3GPP TS 31.102 sec. 4.2.58 and sec. 4.2.59 for USIM,
michael@0 13598 * 3GPP TS 51.011 sec. 10.3.41 and sec. 10.3.42 for SIM.
michael@0 13599 *
michael@0 13600 * @param mcc The mobile country code of the network.
michael@0 13601 * @param mnc The mobile network code of the network.
michael@0 13602 * @param lac The location area code of the network.
michael@0 13603 */
michael@0 13604 getNetworkNameFromICC: function(mcc, mnc, lac) {
michael@0 13605 let RIL = this.context.RIL;
michael@0 13606 let iccInfoPriv = RIL.iccInfoPrivate;
michael@0 13607 let iccInfo = RIL.iccInfo;
michael@0 13608 let pnnEntry;
michael@0 13609
michael@0 13610 if (!mcc || !mnc || !lac) {
michael@0 13611 return null;
michael@0 13612 }
michael@0 13613
michael@0 13614 // We won't get network name if there is no PNN file.
michael@0 13615 if (!iccInfoPriv.PNN) {
michael@0 13616 return null;
michael@0 13617 }
michael@0 13618
michael@0 13619 if (!iccInfoPriv.OPL) {
michael@0 13620 // When OPL is not present:
michael@0 13621 // According to 3GPP TS 31.102 Sec. 4.2.58 and 3GPP TS 51.011 Sec. 10.3.41,
michael@0 13622 // If EF_OPL is not present, the first record in this EF is used for the
michael@0 13623 // default network name when registered to the HPLMN.
michael@0 13624 // If we haven't get pnnEntry assigned, we should try to assign default
michael@0 13625 // value to it.
michael@0 13626 if (mcc == iccInfo.mcc && mnc == iccInfo.mnc) {
michael@0 13627 pnnEntry = iccInfoPriv.PNN[0];
michael@0 13628 }
michael@0 13629 } else {
michael@0 13630 // According to 3GPP TS 31.102 Sec. 4.2.59 and 3GPP TS 51.011 Sec. 10.3.42,
michael@0 13631 // the ME shall use this EF_OPL in association with the EF_PNN in place
michael@0 13632 // of any network name stored within the ME's internal list and any network
michael@0 13633 // name received when registered to the PLMN.
michael@0 13634 let length = iccInfoPriv.OPL ? iccInfoPriv.OPL.length : 0;
michael@0 13635 for (let i = 0; i < length; i++) {
michael@0 13636 let opl = iccInfoPriv.OPL[i];
michael@0 13637 // Try to match the MCC/MNC.
michael@0 13638 if (mcc != opl.mcc || mnc != opl.mnc) {
michael@0 13639 continue;
michael@0 13640 }
michael@0 13641 // Try to match the location area code. If current local area code is
michael@0 13642 // covered by lac range that specified in the OPL entry, use the PNN
michael@0 13643 // that specified in the OPL entry.
michael@0 13644 if ((opl.lacTacStart === 0x0 && opl.lacTacEnd == 0xFFFE) ||
michael@0 13645 (opl.lacTacStart <= lac && opl.lacTacEnd >= lac)) {
michael@0 13646 if (opl.pnnRecordId === 0) {
michael@0 13647 // See 3GPP TS 31.102 Sec. 4.2.59 and 3GPP TS 51.011 Sec. 10.3.42,
michael@0 13648 // A value of '00' indicates that the name is to be taken from other
michael@0 13649 // sources.
michael@0 13650 return null;
michael@0 13651 }
michael@0 13652 pnnEntry = iccInfoPriv.PNN[opl.pnnRecordId - 1];
michael@0 13653 break;
michael@0 13654 }
michael@0 13655 }
michael@0 13656 }
michael@0 13657
michael@0 13658 if (!pnnEntry) {
michael@0 13659 return null;
michael@0 13660 }
michael@0 13661
michael@0 13662 // Return a new object to avoid global variable, PNN, be modified by accident.
michael@0 13663 return { fullName: pnnEntry.fullName || "",
michael@0 13664 shortName: pnnEntry.shortName || "" };
michael@0 13665 },
michael@0 13666
michael@0 13667 /**
michael@0 13668 * This will compute the spnDisplay field of the network.
michael@0 13669 * See TS 22.101 Annex A and TS 51.011 10.3.11 for details.
michael@0 13670 *
michael@0 13671 * @return True if some of iccInfo is changed in by this function.
michael@0 13672 */
michael@0 13673 updateDisplayCondition: function() {
michael@0 13674 let RIL = this.context.RIL;
michael@0 13675
michael@0 13676 // If EFspn isn't existed in SIM or it haven't been read yet, we should
michael@0 13677 // just set isDisplayNetworkNameRequired = true and
michael@0 13678 // isDisplaySpnRequired = false
michael@0 13679 let iccInfo = RIL.iccInfo;
michael@0 13680 let iccInfoPriv = RIL.iccInfoPrivate;
michael@0 13681 let displayCondition = iccInfoPriv.spnDisplayCondition;
michael@0 13682 let origIsDisplayNetworkNameRequired = iccInfo.isDisplayNetworkNameRequired;
michael@0 13683 let origIsDisplaySPNRequired = iccInfo.isDisplaySpnRequired;
michael@0 13684
michael@0 13685 if (displayCondition === undefined) {
michael@0 13686 iccInfo.isDisplayNetworkNameRequired = true;
michael@0 13687 iccInfo.isDisplaySpnRequired = false;
michael@0 13688 } else if (RIL._isCdma) {
michael@0 13689 // CDMA family display rule.
michael@0 13690 let cdmaHome = RIL.cdmaHome;
michael@0 13691 let cell = RIL.voiceRegistrationState.cell;
michael@0 13692 let sid = cell && cell.cdmaSystemId;
michael@0 13693 let nid = cell && cell.cdmaNetworkId;
michael@0 13694
michael@0 13695 iccInfo.isDisplayNetworkNameRequired = false;
michael@0 13696
michael@0 13697 // If display condition is 0x0, we don't even need to check network id
michael@0 13698 // or system id.
michael@0 13699 if (displayCondition === 0x0) {
michael@0 13700 iccInfo.isDisplaySpnRequired = false;
michael@0 13701 } else {
michael@0 13702 // CDMA SPN Display condition dosen't specify whenever network name is
michael@0 13703 // reqired.
michael@0 13704 if (!cdmaHome ||
michael@0 13705 !cdmaHome.systemId ||
michael@0 13706 cdmaHome.systemId.length === 0 ||
michael@0 13707 cdmaHome.systemId.length != cdmaHome.networkId.length ||
michael@0 13708 !sid || !nid) {
michael@0 13709 // CDMA Home haven't been ready, or we haven't got the system id and
michael@0 13710 // network id of the network we register to, assuming we are in home
michael@0 13711 // network.
michael@0 13712 iccInfo.isDisplaySpnRequired = true;
michael@0 13713 } else {
michael@0 13714 // Determine if we are registered in the home service area.
michael@0 13715 // System ID and Network ID are described in 3GPP2 C.S0005 Sec. 2.6.5.2.
michael@0 13716 let inHomeArea = false;
michael@0 13717 for (let i = 0; i < cdmaHome.systemId.length; i++) {
michael@0 13718 let homeSid = cdmaHome.systemId[i],
michael@0 13719 homeNid = cdmaHome.networkId[i];
michael@0 13720 if (homeSid === 0 || homeNid === 0 // Reserved system id/network id
michael@0 13721 || homeSid != sid) {
michael@0 13722 continue;
michael@0 13723 }
michael@0 13724 // According to 3GPP2 C.S0005 Sec. 2.6.5.2, NID number 65535 means
michael@0 13725 // all networks in the system should be considered as home.
michael@0 13726 if (homeNid == 65535 || homeNid == nid) {
michael@0 13727 inHomeArea = true;
michael@0 13728 break;
michael@0 13729 }
michael@0 13730 }
michael@0 13731 iccInfo.isDisplaySpnRequired = inHomeArea;
michael@0 13732 }
michael@0 13733 }
michael@0 13734 } else {
michael@0 13735 // GSM family display rule.
michael@0 13736 let operatorMnc = RIL.operator.mnc;
michael@0 13737 let operatorMcc = RIL.operator.mcc;
michael@0 13738
michael@0 13739 // First detect if we are on HPLMN or one of the PLMN
michael@0 13740 // specified by the SIM card.
michael@0 13741 let isOnMatchingPlmn = false;
michael@0 13742
michael@0 13743 // If the current network is the one defined as mcc/mnc
michael@0 13744 // in SIM card, it's okay.
michael@0 13745 if (iccInfo.mcc == operatorMcc && iccInfo.mnc == operatorMnc) {
michael@0 13746 isOnMatchingPlmn = true;
michael@0 13747 }
michael@0 13748
michael@0 13749 // Test to see if operator's mcc/mnc match mcc/mnc of PLMN.
michael@0 13750 if (!isOnMatchingPlmn && iccInfoPriv.SPDI) {
michael@0 13751 let iccSpdi = iccInfoPriv.SPDI; // PLMN list
michael@0 13752 for (let plmn in iccSpdi) {
michael@0 13753 let plmnMcc = iccSpdi[plmn].mcc;
michael@0 13754 let plmnMnc = iccSpdi[plmn].mnc;
michael@0 13755 isOnMatchingPlmn = (plmnMcc == operatorMcc) && (plmnMnc == operatorMnc);
michael@0 13756 if (isOnMatchingPlmn) {
michael@0 13757 break;
michael@0 13758 }
michael@0 13759 }
michael@0 13760 }
michael@0 13761
michael@0 13762 if (isOnMatchingPlmn) {
michael@0 13763 // The first bit of display condition tells us if we should display
michael@0 13764 // registered PLMN.
michael@0 13765 if (DEBUG) {
michael@0 13766 this.context.debug("PLMN is HPLMN or PLMN " + "is in PLMN list");
michael@0 13767 }
michael@0 13768
michael@0 13769 // TS 31.102 Sec. 4.2.66 and TS 51.011 Sec. 10.3.50
michael@0 13770 // EF_SPDI contains a list of PLMNs in which the Service Provider Name
michael@0 13771 // shall be displayed.
michael@0 13772 iccInfo.isDisplaySpnRequired = true;
michael@0 13773 iccInfo.isDisplayNetworkNameRequired = (displayCondition & 0x01) !== 0;
michael@0 13774 } else {
michael@0 13775 // The second bit of display condition tells us if we should display
michael@0 13776 // registered PLMN.
michael@0 13777 if (DEBUG) {
michael@0 13778 this.context.debug("PLMN isn't HPLMN and PLMN isn't in PLMN list");
michael@0 13779 }
michael@0 13780
michael@0 13781 // We didn't found the requirement of displaying network name if
michael@0 13782 // current PLMN isn't HPLMN nor one of PLMN in SPDI. So we keep
michael@0 13783 // isDisplayNetworkNameRequired false.
michael@0 13784 iccInfo.isDisplayNetworkNameRequired = false;
michael@0 13785 iccInfo.isDisplaySpnRequired = (displayCondition & 0x02) === 0;
michael@0 13786 }
michael@0 13787 }
michael@0 13788
michael@0 13789 if (DEBUG) {
michael@0 13790 this.context.debug("isDisplayNetworkNameRequired = " +
michael@0 13791 iccInfo.isDisplayNetworkNameRequired);
michael@0 13792 this.context.debug("isDisplaySpnRequired = " + iccInfo.isDisplaySpnRequired);
michael@0 13793 }
michael@0 13794
michael@0 13795 return ((origIsDisplayNetworkNameRequired !== iccInfo.isDisplayNetworkNameRequired) ||
michael@0 13796 (origIsDisplaySPNRequired !== iccInfo.isDisplaySpnRequired));
michael@0 13797 },
michael@0 13798
michael@0 13799 decodeSimTlvs: function(tlvsLen) {
michael@0 13800 let GsmPDUHelper = this.context.GsmPDUHelper;
michael@0 13801
michael@0 13802 let index = 0;
michael@0 13803 let tlvs = [];
michael@0 13804 while (index < tlvsLen) {
michael@0 13805 let simTlv = {
michael@0 13806 tag : GsmPDUHelper.readHexOctet(),
michael@0 13807 length : GsmPDUHelper.readHexOctet(),
michael@0 13808 };
michael@0 13809 simTlv.value = GsmPDUHelper.readHexOctetArray(simTlv.length);
michael@0 13810 tlvs.push(simTlv);
michael@0 13811 index += simTlv.length + 2; // The length of 'tag' and 'length' field.
michael@0 13812 }
michael@0 13813 return tlvs;
michael@0 13814 },
michael@0 13815
michael@0 13816 /**
michael@0 13817 * Parse those TLVs and convert it to an object.
michael@0 13818 */
michael@0 13819 parsePbrTlvs: function(pbrTlvs) {
michael@0 13820 let pbr = {};
michael@0 13821 for (let i = 0; i < pbrTlvs.length; i++) {
michael@0 13822 let pbrTlv = pbrTlvs[i];
michael@0 13823 let anrIndex = 0;
michael@0 13824 for (let j = 0; j < pbrTlv.value.length; j++) {
michael@0 13825 let tlv = pbrTlv.value[j];
michael@0 13826 let tagName = USIM_TAG_NAME[tlv.tag];
michael@0 13827
michael@0 13828 // ANR could have multiple files. We save it as anr0, anr1,...etc.
michael@0 13829 if (tlv.tag == ICC_USIM_EFANR_TAG) {
michael@0 13830 tagName += anrIndex;
michael@0 13831 anrIndex++;
michael@0 13832 }
michael@0 13833 pbr[tagName] = tlv;
michael@0 13834 pbr[tagName].fileType = pbrTlv.tag;
michael@0 13835 pbr[tagName].fileId = (tlv.value[0] << 8) | tlv.value[1];
michael@0 13836 pbr[tagName].sfi = tlv.value[2];
michael@0 13837
michael@0 13838 // For Type 2, the order of files is in the same order in IAP.
michael@0 13839 if (pbrTlv.tag == ICC_USIM_TYPE2_TAG) {
michael@0 13840 pbr[tagName].indexInIAP = j;
michael@0 13841 }
michael@0 13842 }
michael@0 13843 }
michael@0 13844
michael@0 13845 return pbr;
michael@0 13846 },
michael@0 13847
michael@0 13848 /**
michael@0 13849 * Update the ICC information to RadioInterfaceLayer.
michael@0 13850 */
michael@0 13851 handleICCInfoChange: function() {
michael@0 13852 let RIL = this.context.RIL;
michael@0 13853 RIL.iccInfo.rilMessageType = "iccinfochange";
michael@0 13854 RIL.sendChromeMessage(RIL.iccInfo);
michael@0 13855 },
michael@0 13856
michael@0 13857 /**
michael@0 13858 * Get whether specificed (U)SIM service is available.
michael@0 13859 *
michael@0 13860 * @param geckoService
michael@0 13861 * Service name like "ADN", "BDN", etc.
michael@0 13862 *
michael@0 13863 * @return true if the service is enabled, false otherwise.
michael@0 13864 */
michael@0 13865 isICCServiceAvailable: function(geckoService) {
michael@0 13866 let RIL = this.context.RIL;
michael@0 13867 let serviceTable = RIL._isCdma ? RIL.iccInfoPrivate.cst:
michael@0 13868 RIL.iccInfoPrivate.sst;
michael@0 13869 let index, bitmask;
michael@0 13870 if (RIL.appType == CARD_APPTYPE_SIM || RIL.appType == CARD_APPTYPE_RUIM) {
michael@0 13871 /**
michael@0 13872 * Service id is valid in 1..N, and 2 bits are used to code each service.
michael@0 13873 *
michael@0 13874 * +----+-- --+----+----+
michael@0 13875 * | b8 | ... | b2 | b1 |
michael@0 13876 * +----+-- --+----+----+
michael@0 13877 *
michael@0 13878 * b1 = 0, service not allocated.
michael@0 13879 * 1, service allocated.
michael@0 13880 * b2 = 0, service not activatd.
michael@0 13881 * 1, service allocated.
michael@0 13882 *
michael@0 13883 * @see 3GPP TS 51.011 10.3.7.
michael@0 13884 */
michael@0 13885 let simService;
michael@0 13886 if (RIL.appType == CARD_APPTYPE_SIM) {
michael@0 13887 simService = GECKO_ICC_SERVICES.sim[geckoService];
michael@0 13888 } else {
michael@0 13889 simService = GECKO_ICC_SERVICES.ruim[geckoService];
michael@0 13890 }
michael@0 13891 if (!simService) {
michael@0 13892 return false;
michael@0 13893 }
michael@0 13894 simService -= 1;
michael@0 13895 index = Math.floor(simService / 4);
michael@0 13896 bitmask = 2 << ((simService % 4) << 1);
michael@0 13897 } else if (RIL.appType == CARD_APPTYPE_USIM) {
michael@0 13898 /**
michael@0 13899 * Service id is valid in 1..N, and 1 bit is used to code each service.
michael@0 13900 *
michael@0 13901 * +----+-- --+----+----+
michael@0 13902 * | b8 | ... | b2 | b1 |
michael@0 13903 * +----+-- --+----+----+
michael@0 13904 *
michael@0 13905 * b1 = 0, service not avaiable.
michael@0 13906 * 1, service available.
michael@0 13907 * b2 = 0, service not avaiable.
michael@0 13908 * 1, service available.
michael@0 13909 *
michael@0 13910 * @see 3GPP TS 31.102 4.2.8.
michael@0 13911 */
michael@0 13912 let usimService = GECKO_ICC_SERVICES.usim[geckoService];
michael@0 13913 if (!usimService) {
michael@0 13914 return false;
michael@0 13915 }
michael@0 13916 usimService -= 1;
michael@0 13917 index = Math.floor(usimService / 8);
michael@0 13918 bitmask = 1 << ((usimService % 8) << 0);
michael@0 13919 }
michael@0 13920
michael@0 13921 return (serviceTable !== null) &&
michael@0 13922 (index < serviceTable.length) &&
michael@0 13923 ((serviceTable[index] & bitmask) !== 0);
michael@0 13924 },
michael@0 13925
michael@0 13926 /**
michael@0 13927 * Check if the string is of GSM default 7-bit coded alphabets with bit 8
michael@0 13928 * set to 0.
michael@0 13929 *
michael@0 13930 * @param str String to be checked.
michael@0 13931 */
michael@0 13932 isGsm8BitAlphabet: function(str) {
michael@0 13933 if (!str) {
michael@0 13934 return false;
michael@0 13935 }
michael@0 13936
michael@0 13937 const langTable = PDU_NL_LOCKING_SHIFT_TABLES[PDU_NL_IDENTIFIER_DEFAULT];
michael@0 13938 const langShiftTable = PDU_NL_SINGLE_SHIFT_TABLES[PDU_NL_IDENTIFIER_DEFAULT];
michael@0 13939
michael@0 13940 for (let i = 0; i < str.length; i++) {
michael@0 13941 let c = str.charAt(i);
michael@0 13942 let octet = langTable.indexOf(c);
michael@0 13943 if (octet == -1) {
michael@0 13944 octet = langShiftTable.indexOf(c);
michael@0 13945 if (octet == -1) {
michael@0 13946 return false;
michael@0 13947 }
michael@0 13948 }
michael@0 13949 }
michael@0 13950
michael@0 13951 return true;
michael@0 13952 },
michael@0 13953
michael@0 13954 /**
michael@0 13955 * Parse MCC/MNC from IMSI. If there is no available value for the length of
michael@0 13956 * mnc, it will use the data in MCC table to parse.
michael@0 13957 *
michael@0 13958 * @param imsi
michael@0 13959 * The imsi of icc.
michael@0 13960 * @param mncLength [optional]
michael@0 13961 * The length of mnc.
michael@0 13962 *
michael@0 13963 * @return An object contains the parsing result of mcc and mnc.
michael@0 13964 * Or null if any error occurred.
michael@0 13965 */
michael@0 13966 parseMccMncFromImsi: function(imsi, mncLength) {
michael@0 13967 if (!imsi) {
michael@0 13968 return null;
michael@0 13969 }
michael@0 13970
michael@0 13971 // MCC is the first 3 digits of IMSI.
michael@0 13972 let mcc = imsi.substr(0,3);
michael@0 13973 if (!mncLength) {
michael@0 13974 // Check the MCC table to decide the length of MNC.
michael@0 13975 let index = MCC_TABLE_FOR_MNC_LENGTH_IS_3.indexOf(mcc);
michael@0 13976 mncLength = (index !== -1) ? 3 : 2;
michael@0 13977 }
michael@0 13978 let mnc = imsi.substr(3, mncLength);
michael@0 13979 if (DEBUG) {
michael@0 13980 this.context.debug("IMSI: " + imsi + " MCC: " + mcc + " MNC: " + mnc);
michael@0 13981 }
michael@0 13982
michael@0 13983 return { mcc: mcc, mnc: mnc};
michael@0 13984 },
michael@0 13985 };
michael@0 13986
michael@0 13987 /**
michael@0 13988 * Helper for ICC Contacts.
michael@0 13989 */
michael@0 13990 function ICCContactHelperObject(aContext) {
michael@0 13991 this.context = aContext;
michael@0 13992 }
michael@0 13993 ICCContactHelperObject.prototype = {
michael@0 13994 context: null,
michael@0 13995
michael@0 13996 /**
michael@0 13997 * Helper function to check DF_PHONEBOOK.
michael@0 13998 */
michael@0 13999 hasDfPhoneBook: function(appType) {
michael@0 14000 switch (appType) {
michael@0 14001 case CARD_APPTYPE_SIM:
michael@0 14002 return false;
michael@0 14003 case CARD_APPTYPE_USIM:
michael@0 14004 return true;
michael@0 14005 case CARD_APPTYPE_RUIM:
michael@0 14006 let ICCUtilsHelper = this.context.ICCUtilsHelper;
michael@0 14007 return ICCUtilsHelper.isICCServiceAvailable("ENHANCED_PHONEBOOK");
michael@0 14008 default:
michael@0 14009 return false;
michael@0 14010 }
michael@0 14011 },
michael@0 14012
michael@0 14013 /**
michael@0 14014 * Helper function to read ICC contacts.
michael@0 14015 *
michael@0 14016 * @param appType One of CARD_APPTYPE_*.
michael@0 14017 * @param contactType "adn" or "fdn".
michael@0 14018 * @param onsuccess Callback to be called when success.
michael@0 14019 * @param onerror Callback to be called when error.
michael@0 14020 */
michael@0 14021 readICCContacts: function(appType, contactType, onsuccess, onerror) {
michael@0 14022 let ICCRecordHelper = this.context.ICCRecordHelper;
michael@0 14023
michael@0 14024 switch (contactType) {
michael@0 14025 case "adn":
michael@0 14026 if (!this.hasDfPhoneBook(appType)) {
michael@0 14027 ICCRecordHelper.readADNLike(ICC_EF_ADN, onsuccess, onerror);
michael@0 14028 } else {
michael@0 14029 this.readUSimContacts(onsuccess, onerror);
michael@0 14030 }
michael@0 14031 break;
michael@0 14032 case "fdn":
michael@0 14033 ICCRecordHelper.readADNLike(ICC_EF_FDN, onsuccess, onerror);
michael@0 14034 break;
michael@0 14035 default:
michael@0 14036 if (DEBUG) {
michael@0 14037 this.context.debug("Unsupported contactType :" + contactType);
michael@0 14038 }
michael@0 14039 onerror(CONTACT_ERR_CONTACT_TYPE_NOT_SUPPORTED);
michael@0 14040 break;
michael@0 14041 }
michael@0 14042 },
michael@0 14043
michael@0 14044 /**
michael@0 14045 * Helper function to find free contact record.
michael@0 14046 *
michael@0 14047 * @param appType One of CARD_APPTYPE_*.
michael@0 14048 * @param contactType "adn" or "fdn".
michael@0 14049 * @param onsuccess Callback to be called when success.
michael@0 14050 * @param onerror Callback to be called when error.
michael@0 14051 */
michael@0 14052 findFreeICCContact: function(appType, contactType, onsuccess, onerror) {
michael@0 14053 let ICCRecordHelper = this.context.ICCRecordHelper;
michael@0 14054
michael@0 14055 switch (contactType) {
michael@0 14056 case "adn":
michael@0 14057 if (!this.hasDfPhoneBook(appType)) {
michael@0 14058 ICCRecordHelper.findFreeRecordId(ICC_EF_ADN, onsuccess.bind(null, 0), onerror);
michael@0 14059 } else {
michael@0 14060 let gotPbrCb = function gotPbrCb(pbrs) {
michael@0 14061 this.findUSimFreeADNRecordId(pbrs, onsuccess, onerror);
michael@0 14062 }.bind(this);
michael@0 14063
michael@0 14064 ICCRecordHelper.readPBR(gotPbrCb, onerror);
michael@0 14065 }
michael@0 14066 break;
michael@0 14067 case "fdn":
michael@0 14068 ICCRecordHelper.findFreeRecordId(ICC_EF_FDN, onsuccess.bind(null, 0), onerror);
michael@0 14069 break;
michael@0 14070 default:
michael@0 14071 if (DEBUG) {
michael@0 14072 this.context.debug("Unsupported contactType :" + contactType);
michael@0 14073 }
michael@0 14074 onerror(CONTACT_ERR_CONTACT_TYPE_NOT_SUPPORTED);
michael@0 14075 break;
michael@0 14076 }
michael@0 14077 },
michael@0 14078
michael@0 14079 /**
michael@0 14080 * Find free ADN record id in USIM.
michael@0 14081 *
michael@0 14082 * @param pbrs All Phonebook Reference Files read.
michael@0 14083 * @param onsuccess Callback to be called when success.
michael@0 14084 * @param onerror Callback to be called when error.
michael@0 14085 */
michael@0 14086 findUSimFreeADNRecordId: function(pbrs, onsuccess, onerror) {
michael@0 14087 let ICCRecordHelper = this.context.ICCRecordHelper;
michael@0 14088
michael@0 14089 (function findFreeRecordId(pbrIndex) {
michael@0 14090 if (pbrIndex >= pbrs.length) {
michael@0 14091 if (DEBUG) {
michael@0 14092 this.context.debug(CONTACT_ERR_NO_FREE_RECORD_FOUND);
michael@0 14093 }
michael@0 14094 onerror(CONTACT_ERR_NO_FREE_RECORD_FOUND);
michael@0 14095 return;
michael@0 14096 }
michael@0 14097
michael@0 14098 let pbr = pbrs[pbrIndex];
michael@0 14099 ICCRecordHelper.findFreeRecordId(
michael@0 14100 pbr.adn.fileId,
michael@0 14101 onsuccess.bind(this, pbrIndex),
michael@0 14102 findFreeRecordId.bind(null, pbrIndex + 1));
michael@0 14103 })(0);
michael@0 14104 },
michael@0 14105
michael@0 14106 /**
michael@0 14107 * Helper function to add a new ICC contact.
michael@0 14108 *
michael@0 14109 * @param appType One of CARD_APPTYPE_*.
michael@0 14110 * @param contactType "adn" or "fdn".
michael@0 14111 * @param contact The contact will be added.
michael@0 14112 * @param pin2 PIN2 is required for FDN.
michael@0 14113 * @param onsuccess Callback to be called when success.
michael@0 14114 * @param onerror Callback to be called when error.
michael@0 14115 */
michael@0 14116 addICCContact: function(appType, contactType, contact, pin2, onsuccess, onerror) {
michael@0 14117 let foundFreeCb = (function foundFreeCb(pbrIndex, recordId) {
michael@0 14118 contact.pbrIndex = pbrIndex;
michael@0 14119 contact.recordId = recordId;
michael@0 14120 this.updateICCContact(appType, contactType, contact, pin2, onsuccess, onerror);
michael@0 14121 }).bind(this);
michael@0 14122
michael@0 14123 // Find free record first.
michael@0 14124 this.findFreeICCContact(appType, contactType, foundFreeCb, onerror);
michael@0 14125 },
michael@0 14126
michael@0 14127 /**
michael@0 14128 * Helper function to update ICC contact.
michael@0 14129 *
michael@0 14130 * @param appType One of CARD_APPTYPE_*.
michael@0 14131 * @param contactType "adn" or "fdn".
michael@0 14132 * @param contact The contact will be updated.
michael@0 14133 * @param pin2 PIN2 is required for FDN.
michael@0 14134 * @param onsuccess Callback to be called when success.
michael@0 14135 * @param onerror Callback to be called when error.
michael@0 14136 */
michael@0 14137 updateICCContact: function(appType, contactType, contact, pin2, onsuccess, onerror) {
michael@0 14138 let ICCRecordHelper = this.context.ICCRecordHelper;
michael@0 14139
michael@0 14140 switch (contactType) {
michael@0 14141 case "adn":
michael@0 14142 if (!this.hasDfPhoneBook(appType)) {
michael@0 14143 ICCRecordHelper.updateADNLike(ICC_EF_ADN, contact, null, onsuccess, onerror);
michael@0 14144 } else {
michael@0 14145 this.updateUSimContact(contact, onsuccess, onerror);
michael@0 14146 }
michael@0 14147 break;
michael@0 14148 case "fdn":
michael@0 14149 if (!pin2) {
michael@0 14150 onerror(GECKO_ERROR_SIM_PIN2);
michael@0 14151 return;
michael@0 14152 }
michael@0 14153 ICCRecordHelper.updateADNLike(ICC_EF_FDN, contact, pin2, onsuccess, onerror);
michael@0 14154 break;
michael@0 14155 default:
michael@0 14156 if (DEBUG) {
michael@0 14157 this.context.debug("Unsupported contactType :" + contactType);
michael@0 14158 }
michael@0 14159 onerror(CONTACT_ERR_CONTACT_TYPE_NOT_SUPPORTED);
michael@0 14160 break;
michael@0 14161 }
michael@0 14162 },
michael@0 14163
michael@0 14164 /**
michael@0 14165 * Read contacts from USIM.
michael@0 14166 *
michael@0 14167 * @param onsuccess Callback to be called when success.
michael@0 14168 * @param onerror Callback to be called when error.
michael@0 14169 */
michael@0 14170 readUSimContacts: function(onsuccess, onerror) {
michael@0 14171 let gotPbrCb = function gotPbrCb(pbrs) {
michael@0 14172 this.readAllPhonebookSets(pbrs, onsuccess, onerror);
michael@0 14173 }.bind(this);
michael@0 14174
michael@0 14175 this.context.ICCRecordHelper.readPBR(gotPbrCb, onerror);
michael@0 14176 },
michael@0 14177
michael@0 14178 /**
michael@0 14179 * Read all Phonebook sets.
michael@0 14180 *
michael@0 14181 * @param pbrs All Phonebook Reference Files read.
michael@0 14182 * @param onsuccess Callback to be called when success.
michael@0 14183 * @param onerror Callback to be called when error.
michael@0 14184 */
michael@0 14185 readAllPhonebookSets: function(pbrs, onsuccess, onerror) {
michael@0 14186 let allContacts = [], pbrIndex = 0;
michael@0 14187 let readPhonebook = function readPhonebook(contacts) {
michael@0 14188 if (contacts) {
michael@0 14189 allContacts = allContacts.concat(contacts);
michael@0 14190 }
michael@0 14191
michael@0 14192 let cLen = contacts ? contacts.length : 0;
michael@0 14193 for (let i = 0; i < cLen; i++) {
michael@0 14194 contacts[i].pbrIndex = pbrIndex;
michael@0 14195 }
michael@0 14196
michael@0 14197 pbrIndex++;
michael@0 14198 if (pbrIndex >= pbrs.length) {
michael@0 14199 if (onsuccess) {
michael@0 14200 onsuccess(allContacts);
michael@0 14201 }
michael@0 14202 return;
michael@0 14203 }
michael@0 14204
michael@0 14205 this.readPhonebookSet(pbrs[pbrIndex], readPhonebook, onerror);
michael@0 14206 }.bind(this);
michael@0 14207
michael@0 14208 this.readPhonebookSet(pbrs[pbrIndex], readPhonebook, onerror);
michael@0 14209 },
michael@0 14210
michael@0 14211 /**
michael@0 14212 * Read from Phonebook Reference File.
michael@0 14213 *
michael@0 14214 * @param pbr Phonebook Reference File to be read.
michael@0 14215 * @param onsuccess Callback to be called when success.
michael@0 14216 * @param onerror Callback to be called when error.
michael@0 14217 */
michael@0 14218 readPhonebookSet: function(pbr, onsuccess, onerror) {
michael@0 14219 let gotAdnCb = function gotAdnCb(contacts) {
michael@0 14220 this.readSupportedPBRFields(pbr, contacts, onsuccess, onerror);
michael@0 14221 }.bind(this);
michael@0 14222
michael@0 14223 this.context.ICCRecordHelper.readADNLike(pbr.adn.fileId, gotAdnCb, onerror);
michael@0 14224 },
michael@0 14225
michael@0 14226 /**
michael@0 14227 * Read supported Phonebook fields.
michael@0 14228 *
michael@0 14229 * @param pbr Phone Book Reference file.
michael@0 14230 * @param contacts Contacts stored on ICC.
michael@0 14231 * @param onsuccess Callback to be called when success.
michael@0 14232 * @param onerror Callback to be called when error.
michael@0 14233 */
michael@0 14234 readSupportedPBRFields: function(pbr, contacts, onsuccess, onerror) {
michael@0 14235 let fieldIndex = 0;
michael@0 14236 (function readField() {
michael@0 14237 let field = USIM_PBR_FIELDS[fieldIndex];
michael@0 14238 fieldIndex += 1;
michael@0 14239 if (!field) {
michael@0 14240 if (onsuccess) {
michael@0 14241 onsuccess(contacts);
michael@0 14242 }
michael@0 14243 return;
michael@0 14244 }
michael@0 14245
michael@0 14246 this.readPhonebookField(pbr, contacts, field, readField.bind(this), onerror);
michael@0 14247 }).call(this);
michael@0 14248 },
michael@0 14249
michael@0 14250 /**
michael@0 14251 * Read Phonebook field.
michael@0 14252 *
michael@0 14253 * @param pbr The phonebook reference file.
michael@0 14254 * @param contacts Contacts stored on ICC.
michael@0 14255 * @param field Phonebook field to be retrieved.
michael@0 14256 * @param onsuccess Callback to be called when success.
michael@0 14257 * @param onerror Callback to be called when error.
michael@0 14258 */
michael@0 14259 readPhonebookField: function(pbr, contacts, field, onsuccess, onerror) {
michael@0 14260 if (!pbr[field]) {
michael@0 14261 if (onsuccess) {
michael@0 14262 onsuccess(contacts);
michael@0 14263 }
michael@0 14264 return;
michael@0 14265 }
michael@0 14266
michael@0 14267 (function doReadContactField(n) {
michael@0 14268 if (n >= contacts.length) {
michael@0 14269 // All contact's fields are read.
michael@0 14270 if (onsuccess) {
michael@0 14271 onsuccess(contacts);
michael@0 14272 }
michael@0 14273 return;
michael@0 14274 }
michael@0 14275
michael@0 14276 // get n-th contact's field.
michael@0 14277 this.readContactField(pbr, contacts[n], field,
michael@0 14278 doReadContactField.bind(this, n + 1), onerror);
michael@0 14279 }).call(this, 0);
michael@0 14280 },
michael@0 14281
michael@0 14282 /**
michael@0 14283 * Read contact's field from USIM.
michael@0 14284 *
michael@0 14285 * @param pbr The phonebook reference file.
michael@0 14286 * @param contact The contact needs to get field.
michael@0 14287 * @param field Phonebook field to be retrieved.
michael@0 14288 * @param onsuccess Callback to be called when success.
michael@0 14289 * @param onerror Callback to be called when error.
michael@0 14290 */
michael@0 14291 readContactField: function(pbr, contact, field, onsuccess, onerror) {
michael@0 14292 let gotRecordIdCb = function gotRecordIdCb(recordId) {
michael@0 14293 if (recordId == 0xff) {
michael@0 14294 if (onsuccess) {
michael@0 14295 onsuccess();
michael@0 14296 }
michael@0 14297 return;
michael@0 14298 }
michael@0 14299
michael@0 14300 let fileId = pbr[field].fileId;
michael@0 14301 let fileType = pbr[field].fileType;
michael@0 14302 let gotFieldCb = function gotFieldCb(value) {
michael@0 14303 if (value) {
michael@0 14304 // Move anr0 anr1,.. into anr[].
michael@0 14305 if (field.startsWith(USIM_PBR_ANR)) {
michael@0 14306 if (!contact[USIM_PBR_ANR]) {
michael@0 14307 contact[USIM_PBR_ANR] = [];
michael@0 14308 }
michael@0 14309 contact[USIM_PBR_ANR].push(value);
michael@0 14310 } else {
michael@0 14311 contact[field] = value;
michael@0 14312 }
michael@0 14313 }
michael@0 14314
michael@0 14315 if (onsuccess) {
michael@0 14316 onsuccess();
michael@0 14317 }
michael@0 14318 }.bind(this);
michael@0 14319
michael@0 14320 let ICCRecordHelper = this.context.ICCRecordHelper;
michael@0 14321 // Detect EF to be read, for anr, it could have anr0, anr1,...
michael@0 14322 let ef = field.startsWith(USIM_PBR_ANR) ? USIM_PBR_ANR : field;
michael@0 14323 switch (ef) {
michael@0 14324 case USIM_PBR_EMAIL:
michael@0 14325 ICCRecordHelper.readEmail(fileId, fileType, recordId, gotFieldCb, onerror);
michael@0 14326 break;
michael@0 14327 case USIM_PBR_ANR:
michael@0 14328 ICCRecordHelper.readANR(fileId, fileType, recordId, gotFieldCb, onerror);
michael@0 14329 break;
michael@0 14330 default:
michael@0 14331 if (DEBUG) {
michael@0 14332 this.context.debug("Unsupported field :" + field);
michael@0 14333 }
michael@0 14334 onerror(CONTACT_ERR_FIELD_NOT_SUPPORTED);
michael@0 14335 break;
michael@0 14336 }
michael@0 14337 }.bind(this);
michael@0 14338
michael@0 14339 this.getContactFieldRecordId(pbr, contact, field, gotRecordIdCb, onerror);
michael@0 14340 },
michael@0 14341
michael@0 14342 /**
michael@0 14343 * Get the recordId.
michael@0 14344 *
michael@0 14345 * If the fileType of field is ICC_USIM_TYPE1_TAG, use corresponding ADN recordId.
michael@0 14346 * otherwise get the recordId from IAP.
michael@0 14347 *
michael@0 14348 * @see TS 131.102, clause 4.4.2.2
michael@0 14349 *
michael@0 14350 * @param pbr The phonebook reference file.
michael@0 14351 * @param contact The contact will be updated.
michael@0 14352 * @param onsuccess Callback to be called when success.
michael@0 14353 * @param onerror Callback to be called when error.
michael@0 14354 */
michael@0 14355 getContactFieldRecordId: function(pbr, contact, field, onsuccess, onerror) {
michael@0 14356 if (pbr[field].fileType == ICC_USIM_TYPE1_TAG) {
michael@0 14357 // If the file type is ICC_USIM_TYPE1_TAG, use corresponding ADN recordId.
michael@0 14358 if (onsuccess) {
michael@0 14359 onsuccess(contact.recordId);
michael@0 14360 }
michael@0 14361 } else if (pbr[field].fileType == ICC_USIM_TYPE2_TAG) {
michael@0 14362 // If the file type is ICC_USIM_TYPE2_TAG, the recordId shall be got from IAP.
michael@0 14363 let gotIapCb = function gotIapCb(iap) {
michael@0 14364 let indexInIAP = pbr[field].indexInIAP;
michael@0 14365 let recordId = iap[indexInIAP];
michael@0 14366
michael@0 14367 if (onsuccess) {
michael@0 14368 onsuccess(recordId);
michael@0 14369 }
michael@0 14370 }.bind(this);
michael@0 14371
michael@0 14372 this.context.ICCRecordHelper.readIAP(pbr.iap.fileId, contact.recordId,
michael@0 14373 gotIapCb, onerror);
michael@0 14374 } else {
michael@0 14375 if (DEBUG) {
michael@0 14376 this.context.debug("USIM PBR files in Type 3 format are not supported.");
michael@0 14377 }
michael@0 14378 onerror(CONTACT_ERR_REQUEST_NOT_SUPPORTED);
michael@0 14379 }
michael@0 14380 },
michael@0 14381
michael@0 14382 /**
michael@0 14383 * Update USIM contact.
michael@0 14384 *
michael@0 14385 * @param contact The contact will be updated.
michael@0 14386 * @param onsuccess Callback to be called when success.
michael@0 14387 * @param onerror Callback to be called when error.
michael@0 14388 */
michael@0 14389 updateUSimContact: function(contact, onsuccess, onerror) {
michael@0 14390 let gotPbrCb = function gotPbrCb(pbrs) {
michael@0 14391 let pbr = pbrs[contact.pbrIndex];
michael@0 14392 if (!pbr) {
michael@0 14393 if (DEBUG) {
michael@0 14394 this.context.debug(CONTACT_ERR_CANNOT_ACCESS_PHONEBOOK);
michael@0 14395 }
michael@0 14396 onerror(CONTACT_ERR_CANNOT_ACCESS_PHONEBOOK);
michael@0 14397 return;
michael@0 14398 }
michael@0 14399 this.updatePhonebookSet(pbr, contact, onsuccess, onerror);
michael@0 14400 }.bind(this);
michael@0 14401
michael@0 14402 this.context.ICCRecordHelper.readPBR(gotPbrCb, onerror);
michael@0 14403 },
michael@0 14404
michael@0 14405 /**
michael@0 14406 * Update fields in Phonebook Reference File.
michael@0 14407 *
michael@0 14408 * @param pbr Phonebook Reference File to be read.
michael@0 14409 * @param onsuccess Callback to be called when success.
michael@0 14410 * @param onerror Callback to be called when error.
michael@0 14411 */
michael@0 14412 updatePhonebookSet: function(pbr, contact, onsuccess, onerror) {
michael@0 14413 let updateAdnCb = function() {
michael@0 14414 this.updateSupportedPBRFields(pbr, contact, onsuccess, onerror);
michael@0 14415 }.bind(this);
michael@0 14416
michael@0 14417 this.context.ICCRecordHelper.updateADNLike(pbr.adn.fileId, contact, null,
michael@0 14418 updateAdnCb, onerror);
michael@0 14419 },
michael@0 14420
michael@0 14421 /**
michael@0 14422 * Update supported Phonebook fields.
michael@0 14423 *
michael@0 14424 * @param pbr Phone Book Reference file.
michael@0 14425 * @param contact Contact to be updated.
michael@0 14426 * @param onsuccess Callback to be called when success.
michael@0 14427 * @param onerror Callback to be called when error.
michael@0 14428 */
michael@0 14429 updateSupportedPBRFields: function(pbr, contact, onsuccess, onerror) {
michael@0 14430 let fieldIndex = 0;
michael@0 14431 (function updateField() {
michael@0 14432 let field = USIM_PBR_FIELDS[fieldIndex];
michael@0 14433 fieldIndex += 1;
michael@0 14434 if (!field) {
michael@0 14435 if (onsuccess) {
michael@0 14436 onsuccess();
michael@0 14437 }
michael@0 14438 return;
michael@0 14439 }
michael@0 14440
michael@0 14441 // Check if PBR has this field.
michael@0 14442 if (!pbr[field]) {
michael@0 14443 updateField.call(this);
michael@0 14444 return;
michael@0 14445 }
michael@0 14446
michael@0 14447 this.updateContactField(pbr, contact, field, updateField.bind(this), onerror);
michael@0 14448 }).call(this);
michael@0 14449 },
michael@0 14450
michael@0 14451 /**
michael@0 14452 * Update contact's field from USIM.
michael@0 14453 *
michael@0 14454 * @param pbr The phonebook reference file.
michael@0 14455 * @param contact The contact needs to be updated.
michael@0 14456 * @param field Phonebook field to be updated.
michael@0 14457 * @param onsuccess Callback to be called when success.
michael@0 14458 * @param onerror Callback to be called when error.
michael@0 14459 */
michael@0 14460 updateContactField: function(pbr, contact, field, onsuccess, onerror) {
michael@0 14461 if (pbr[field].fileType === ICC_USIM_TYPE1_TAG) {
michael@0 14462 this.updateContactFieldType1(pbr, contact, field, onsuccess, onerror);
michael@0 14463 } else if (pbr[field].fileType === ICC_USIM_TYPE2_TAG) {
michael@0 14464 this.updateContactFieldType2(pbr, contact, field, onsuccess, onerror);
michael@0 14465 } else {
michael@0 14466 if (DEBUG) {
michael@0 14467 this.context.debug("USIM PBR files in Type 3 format are not supported.");
michael@0 14468 }
michael@0 14469 onerror(CONTACT_ERR_REQUEST_NOT_SUPPORTED);
michael@0 14470 }
michael@0 14471 },
michael@0 14472
michael@0 14473 /**
michael@0 14474 * Update Type 1 USIM contact fields.
michael@0 14475 *
michael@0 14476 * @param pbr The phonebook reference file.
michael@0 14477 * @param contact The contact needs to be updated.
michael@0 14478 * @param field Phonebook field to be updated.
michael@0 14479 * @param onsuccess Callback to be called when success.
michael@0 14480 * @param onerror Callback to be called when error.
michael@0 14481 */
michael@0 14482 updateContactFieldType1: function(pbr, contact, field, onsuccess, onerror) {
michael@0 14483 let ICCRecordHelper = this.context.ICCRecordHelper;
michael@0 14484
michael@0 14485 if (field === USIM_PBR_EMAIL) {
michael@0 14486 ICCRecordHelper.updateEmail(pbr, contact.recordId, contact.email, null, onsuccess, onerror);
michael@0 14487 } else if (field === USIM_PBR_ANR0) {
michael@0 14488 let anr = Array.isArray(contact.anr) ? contact.anr[0] : null;
michael@0 14489 ICCRecordHelper.updateANR(pbr, contact.recordId, anr, null, onsuccess, onerror);
michael@0 14490 } else {
michael@0 14491 if (DEBUG) {
michael@0 14492 this.context.debug("Unsupported field :" + field);
michael@0 14493 }
michael@0 14494 onerror(CONTACT_ERR_FIELD_NOT_SUPPORTED);
michael@0 14495 }
michael@0 14496 },
michael@0 14497
michael@0 14498 /**
michael@0 14499 * Update Type 2 USIM contact fields.
michael@0 14500 *
michael@0 14501 * @param pbr The phonebook reference file.
michael@0 14502 * @param contact The contact needs to be updated.
michael@0 14503 * @param field Phonebook field to be updated.
michael@0 14504 * @param onsuccess Callback to be called when success.
michael@0 14505 * @param onerror Callback to be called when error.
michael@0 14506 */
michael@0 14507 updateContactFieldType2: function(pbr, contact, field, onsuccess, onerror) {
michael@0 14508 let ICCRecordHelper = this.context.ICCRecordHelper;
michael@0 14509
michael@0 14510 // Case 1 : EF_IAP[adnRecordId] doesn't have a value(0xff)
michael@0 14511 // Find a free recordId for EF_field
michael@0 14512 // Update field with that free recordId.
michael@0 14513 // Update IAP.
michael@0 14514 //
michael@0 14515 // Case 2: EF_IAP[adnRecordId] has a value
michael@0 14516 // update EF_field[iap[field.indexInIAP]]
michael@0 14517
michael@0 14518 let gotIapCb = function gotIapCb(iap) {
michael@0 14519 let recordId = iap[pbr[field].indexInIAP];
michael@0 14520 if (recordId === 0xff) {
michael@0 14521 // If the value in IAP[index] is 0xff, which means the contact stored on
michael@0 14522 // the SIM doesn't have the additional attribute (email or anr).
michael@0 14523 // So if the contact to be updated doesn't have the attribute either,
michael@0 14524 // we don't have to update it.
michael@0 14525 if ((field === USIM_PBR_EMAIL && contact.email) ||
michael@0 14526 (field === USIM_PBR_ANR0 &&
michael@0 14527 (Array.isArray(contact.anr) && contact.anr[0]))) {
michael@0 14528 // Case 1.
michael@0 14529 this.addContactFieldType2(pbr, contact, field, onsuccess, onerror);
michael@0 14530 } else {
michael@0 14531 if (onsuccess) {
michael@0 14532 onsuccess();
michael@0 14533 }
michael@0 14534 }
michael@0 14535 return;
michael@0 14536 }
michael@0 14537
michael@0 14538 // Case 2.
michael@0 14539 if (field === USIM_PBR_EMAIL) {
michael@0 14540 ICCRecordHelper.updateEmail(pbr, recordId, contact.email, contact.recordId, onsuccess, onerror);
michael@0 14541 } else if (field === USIM_PBR_ANR0) {
michael@0 14542 let anr = Array.isArray(contact.anr) ? contact.anr[0] : null;
michael@0 14543 ICCRecordHelper.updateANR(pbr, recordId, anr, contact.recordId, onsuccess, onerror);
michael@0 14544 } else {
michael@0 14545 if (DEBUG) {
michael@0 14546 this.context.debug("Unsupported field :" + field);
michael@0 14547 }
michael@0 14548 onerror(CONTACT_ERR_FIELD_NOT_SUPPORTED);
michael@0 14549 }
michael@0 14550
michael@0 14551 }.bind(this);
michael@0 14552
michael@0 14553 ICCRecordHelper.readIAP(pbr.iap.fileId, contact.recordId, gotIapCb, onerror);
michael@0 14554 },
michael@0 14555
michael@0 14556 /**
michael@0 14557 * Add Type 2 USIM contact fields.
michael@0 14558 *
michael@0 14559 * @param pbr The phonebook reference file.
michael@0 14560 * @param contact The contact needs to be updated.
michael@0 14561 * @param field Phonebook field to be updated.
michael@0 14562 * @param onsuccess Callback to be called when success.
michael@0 14563 * @param onerror Callback to be called when error.
michael@0 14564 */
michael@0 14565 addContactFieldType2: function(pbr, contact, field, onsuccess, onerror) {
michael@0 14566 let ICCRecordHelper = this.context.ICCRecordHelper;
michael@0 14567
michael@0 14568 let successCb = function successCb(recordId) {
michael@0 14569 let updateCb = function updateCb() {
michael@0 14570 this.updateContactFieldIndexInIAP(pbr, contact.recordId, field, recordId, onsuccess, onerror);
michael@0 14571 }.bind(this);
michael@0 14572
michael@0 14573 if (field === USIM_PBR_EMAIL) {
michael@0 14574 ICCRecordHelper.updateEmail(pbr, recordId, contact.email, contact.recordId, updateCb, onerror);
michael@0 14575 } else if (field === USIM_PBR_ANR0) {
michael@0 14576 ICCRecordHelper.updateANR(pbr, recordId, contact.anr[0], contact.recordId, updateCb, onerror);
michael@0 14577 }
michael@0 14578 }.bind(this);
michael@0 14579
michael@0 14580 let errorCb = function errorCb(errorMsg) {
michael@0 14581 if (DEBUG) {
michael@0 14582 this.context.debug(errorMsg + " USIM field " + field);
michael@0 14583 }
michael@0 14584 onerror(errorMsg);
michael@0 14585 }.bind(this);
michael@0 14586
michael@0 14587 ICCRecordHelper.findFreeRecordId(pbr[field].fileId, successCb, errorCb);
michael@0 14588 },
michael@0 14589
michael@0 14590 /**
michael@0 14591 * Update IAP value.
michael@0 14592 *
michael@0 14593 * @param pbr The phonebook reference file.
michael@0 14594 * @param recordNumber The record identifier of EF_IAP.
michael@0 14595 * @param field Phonebook field.
michael@0 14596 * @param value The value of 'field' in IAP.
michael@0 14597 * @param onsuccess Callback to be called when success.
michael@0 14598 * @param onerror Callback to be called when error.
michael@0 14599 *
michael@0 14600 */
michael@0 14601 updateContactFieldIndexInIAP: function(pbr, recordNumber, field, value, onsuccess, onerror) {
michael@0 14602 let ICCRecordHelper = this.context.ICCRecordHelper;
michael@0 14603
michael@0 14604 let gotIAPCb = function gotIAPCb(iap) {
michael@0 14605 iap[pbr[field].indexInIAP] = value;
michael@0 14606 ICCRecordHelper.updateIAP(pbr.iap.fileId, recordNumber, iap, onsuccess, onerror);
michael@0 14607 }.bind(this);
michael@0 14608 ICCRecordHelper.readIAP(pbr.iap.fileId, recordNumber, gotIAPCb, onerror);
michael@0 14609 },
michael@0 14610 };
michael@0 14611
michael@0 14612 /**
michael@0 14613 * Global stuff.
michael@0 14614 */
michael@0 14615
michael@0 14616 function Context(aClientId) {
michael@0 14617 this.clientId = aClientId;
michael@0 14618
michael@0 14619 this.Buf = new BufObject(this);
michael@0 14620 this.Buf.init();
michael@0 14621
michael@0 14622 this.RIL = new RilObject(this);
michael@0 14623 this.RIL.initRILState();
michael@0 14624 }
michael@0 14625 Context.prototype = {
michael@0 14626 clientId: null,
michael@0 14627 Buf: null,
michael@0 14628 RIL: null,
michael@0 14629
michael@0 14630 debug: function(aMessage) {
michael@0 14631 GLOBAL.debug("[" + this.clientId + "] " + aMessage);
michael@0 14632 }
michael@0 14633 };
michael@0 14634
michael@0 14635 (function() {
michael@0 14636 let lazySymbols = [
michael@0 14637 "BerTlvHelper", "BitBufferHelper", "CdmaPDUHelper",
michael@0 14638 "ComprehensionTlvHelper", "GsmPDUHelper", "ICCContactHelper",
michael@0 14639 "ICCFileHelper", "ICCIOHelper", "ICCPDUHelper", "ICCRecordHelper",
michael@0 14640 "ICCUtilsHelper", "RuimRecordHelper", "SimRecordHelper",
michael@0 14641 "StkCommandParamsFactory", "StkProactiveCmdHelper",
michael@0 14642 ];
michael@0 14643
michael@0 14644 for (let i = 0; i < lazySymbols.length; i++) {
michael@0 14645 let symbol = lazySymbols[i];
michael@0 14646 Object.defineProperty(Context.prototype, symbol, {
michael@0 14647 get: function() {
michael@0 14648 let real = new GLOBAL[symbol + "Object"](this);
michael@0 14649 Object.defineProperty(this, symbol, {
michael@0 14650 value: real,
michael@0 14651 enumerable: true
michael@0 14652 });
michael@0 14653 return real;
michael@0 14654 },
michael@0 14655 configurable: true,
michael@0 14656 enumerable: true
michael@0 14657 });
michael@0 14658 }
michael@0 14659 })();
michael@0 14660
michael@0 14661 let ContextPool = {
michael@0 14662 _contexts: [],
michael@0 14663
michael@0 14664 handleRilMessage: function(aClientId, aUint8Array) {
michael@0 14665 let context = this._contexts[aClientId];
michael@0 14666 context.Buf.processIncoming(aUint8Array);
michael@0 14667 },
michael@0 14668
michael@0 14669 handleChromeMessage: function(aMessage) {
michael@0 14670 let clientId = aMessage.rilMessageClientId;
michael@0 14671 if (clientId != null) {
michael@0 14672 let context = this._contexts[clientId];
michael@0 14673 context.RIL.handleChromeMessage(aMessage);
michael@0 14674 return;
michael@0 14675 }
michael@0 14676
michael@0 14677 if (DEBUG) debug("Received global chrome message " + JSON.stringify(aMessage));
michael@0 14678 let method = this[aMessage.rilMessageType];
michael@0 14679 if (typeof method != "function") {
michael@0 14680 if (DEBUG) {
michael@0 14681 debug("Don't know what to do");
michael@0 14682 }
michael@0 14683 return;
michael@0 14684 }
michael@0 14685 method.call(this, aMessage);
michael@0 14686 },
michael@0 14687
michael@0 14688 setInitialOptions: function(aOptions) {
michael@0 14689 DEBUG = DEBUG_WORKER || aOptions.debug;
michael@0 14690 RIL_EMERGENCY_NUMBERS = aOptions.rilEmergencyNumbers;
michael@0 14691 RIL_CELLBROADCAST_DISABLED = aOptions.cellBroadcastDisabled;
michael@0 14692 RIL_CLIR_MODE = aOptions.clirMode;
michael@0 14693
michael@0 14694 let quirks = aOptions.quirks;
michael@0 14695 RILQUIRKS_CALLSTATE_EXTRA_UINT32 = quirks.callstateExtraUint32;
michael@0 14696 RILQUIRKS_V5_LEGACY = quirks.v5Legacy;
michael@0 14697 RILQUIRKS_REQUEST_USE_DIAL_EMERGENCY_CALL = quirks.requestUseDialEmergencyCall;
michael@0 14698 RILQUIRKS_SIM_APP_STATE_EXTRA_FIELDS = quirks.simAppStateExtraFields;
michael@0 14699 RILQUIRKS_EXTRA_UINT32_2ND_CALL = quirks.extraUint2ndCall;
michael@0 14700 RILQUIRKS_HAVE_QUERY_ICC_LOCK_RETRY_COUNT = quirks.haveQueryIccLockRetryCount;
michael@0 14701 RILQUIRKS_SEND_STK_PROFILE_DOWNLOAD = quirks.sendStkProfileDownload;
michael@0 14702 RILQUIRKS_DATA_REGISTRATION_ON_DEMAND = quirks.dataRegistrationOnDemand;
michael@0 14703 },
michael@0 14704
michael@0 14705 registerClient: function(aOptions) {
michael@0 14706 let clientId = aOptions.clientId;
michael@0 14707 this._contexts[clientId] = new Context(clientId);
michael@0 14708 },
michael@0 14709 };
michael@0 14710
michael@0 14711 function onRILMessage(aClientId, aUint8Array) {
michael@0 14712 ContextPool.handleRilMessage(aClientId, aUint8Array);
michael@0 14713 }
michael@0 14714
michael@0 14715 onmessage = function onmessage(event) {
michael@0 14716 ContextPool.handleChromeMessage(event.data);
michael@0 14717 };
michael@0 14718
michael@0 14719 onerror = function onerror(event) {
michael@0 14720 if (DEBUG) debug("onerror" + event.message + "\n");
michael@0 14721 };

mercurial