dom/system/gonk/ril_worker.js

changeset 0
6474c204b198
     1.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
     1.2 +++ b/dom/system/gonk/ril_worker.js	Wed Dec 31 06:09:35 2014 +0100
     1.3 @@ -0,0 +1,14721 @@
     1.4 +/* Copyright 2012 Mozilla Foundation and Mozilla contributors
     1.5 + *
     1.6 + * Licensed under the Apache License, Version 2.0 (the "License");
     1.7 + * you may not use this file except in compliance with the License.
     1.8 + * You may obtain a copy of the License at
     1.9 + *
    1.10 + *     http://www.apache.org/licenses/LICENSE-2.0
    1.11 + *
    1.12 + * Unless required by applicable law or agreed to in writing, software
    1.13 + * distributed under the License is distributed on an "AS IS" BASIS,
    1.14 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    1.15 + * See the License for the specific language governing permissions and
    1.16 + * limitations under the License.
    1.17 + */
    1.18 +
    1.19 +/**
    1.20 + * This file implements the RIL worker thread. It communicates with
    1.21 + * the main thread to provide a high-level API to the phone's RIL
    1.22 + * stack, and with the RIL IPC thread to communicate with the RIL
    1.23 + * device itself. These communication channels use message events as
    1.24 + * known from Web Workers:
    1.25 + *
    1.26 + * - postMessage()/"message" events for main thread communication
    1.27 + *
    1.28 + * - postRILMessage()/"RILMessageEvent" events for RIL IPC thread
    1.29 + *   communication.
    1.30 + *
    1.31 + * The two main objects in this file represent individual parts of this
    1.32 + * communication chain:
    1.33 + *
    1.34 + * - RILMessageEvent -> Buf -> RIL -> postMessage() -> nsIRadioInterfaceLayer
    1.35 + * - nsIRadioInterfaceLayer -> postMessage() -> RIL -> Buf -> postRILMessage()
    1.36 + *
    1.37 + * Note: The code below is purposely lean on abstractions to be as lean in
    1.38 + * terms of object allocations. As a result, it may look more like C than
    1.39 + * JavaScript, and that's intended.
    1.40 + */
    1.41 +
    1.42 +"use strict";
    1.43 +
    1.44 +importScripts("ril_consts.js");
    1.45 +importScripts("resource://gre/modules/workers/require.js");
    1.46 +
    1.47 +// set to true in ril_consts.js to see debug messages
    1.48 +let DEBUG = DEBUG_WORKER;
    1.49 +let GLOBAL = this;
    1.50 +
    1.51 +if (!this.debug) {
    1.52 +  // Debugging stub that goes nowhere.
    1.53 +  this.debug = function debug(message) {
    1.54 +    dump("RIL Worker: " + message + "\n");
    1.55 +  };
    1.56 +}
    1.57 +
    1.58 +let RIL_CELLBROADCAST_DISABLED;
    1.59 +let RIL_CLIR_MODE;
    1.60 +let RIL_EMERGENCY_NUMBERS;
    1.61 +const DEFAULT_EMERGENCY_NUMBERS = ["112", "911"];
    1.62 +
    1.63 +// Timeout value for emergency callback mode.
    1.64 +const EMERGENCY_CB_MODE_TIMEOUT_MS = 300000;  // 5 mins = 300000 ms.
    1.65 +
    1.66 +const ICC_MAX_LINEAR_FIXED_RECORDS = 0xfe;
    1.67 +
    1.68 +// MMI match groups
    1.69 +const MMI_MATCH_GROUP_FULL_MMI = 1;
    1.70 +const MMI_MATCH_GROUP_MMI_PROCEDURE = 2;
    1.71 +const MMI_MATCH_GROUP_SERVICE_CODE = 3;
    1.72 +const MMI_MATCH_GROUP_SIA = 5;
    1.73 +const MMI_MATCH_GROUP_SIB = 7;
    1.74 +const MMI_MATCH_GROUP_SIC = 9;
    1.75 +const MMI_MATCH_GROUP_PWD_CONFIRM = 11;
    1.76 +const MMI_MATCH_GROUP_DIALING_NUMBER = 12;
    1.77 +
    1.78 +const MMI_MAX_LENGTH_SHORT_CODE = 2;
    1.79 +
    1.80 +const MMI_END_OF_USSD = "#";
    1.81 +
    1.82 +// Should match the value we set in dom/telephony/TelephonyCommon.h
    1.83 +const OUTGOING_PLACEHOLDER_CALL_INDEX = 0xffffffff;
    1.84 +
    1.85 +let RILQUIRKS_CALLSTATE_EXTRA_UINT32;
    1.86 +// This may change at runtime since in RIL v6 and later, we get the version
    1.87 +// number via the UNSOLICITED_RIL_CONNECTED parcel.
    1.88 +let RILQUIRKS_V5_LEGACY;
    1.89 +let RILQUIRKS_REQUEST_USE_DIAL_EMERGENCY_CALL;
    1.90 +let RILQUIRKS_SIM_APP_STATE_EXTRA_FIELDS;
    1.91 +// Needed for call-waiting on Peak device
    1.92 +let RILQUIRKS_EXTRA_UINT32_2ND_CALL;
    1.93 +// On the emulator we support querying the number of lock retries
    1.94 +let RILQUIRKS_HAVE_QUERY_ICC_LOCK_RETRY_COUNT;
    1.95 +
    1.96 +// Ril quirk to Send STK Profile Download
    1.97 +let RILQUIRKS_SEND_STK_PROFILE_DOWNLOAD;
    1.98 +
    1.99 +// Ril quirk to attach data registration on demand.
   1.100 +let RILQUIRKS_DATA_REGISTRATION_ON_DEMAND;
   1.101 +
   1.102 +function BufObject(aContext) {
   1.103 +  this.context = aContext;
   1.104 +}
   1.105 +BufObject.prototype = {
   1.106 +  context: null,
   1.107 +
   1.108 +  mToken: 0,
   1.109 +  mTokenRequestMap: null,
   1.110 +
   1.111 +  init: function() {
   1.112 +    this._init();
   1.113 +
   1.114 +    // This gets incremented each time we send out a parcel.
   1.115 +    this.mToken = 1;
   1.116 +
   1.117 +    // Maps tokens we send out with requests to the request type, so that
   1.118 +    // when we get a response parcel back, we know what request it was for.
   1.119 +    this.mTokenRequestMap = new Map();
   1.120 +  },
   1.121 +
   1.122 +  /**
   1.123 +   * Process one parcel.
   1.124 +   */
   1.125 +  processParcel: function() {
   1.126 +    let response_type = this.readInt32();
   1.127 +
   1.128 +    let request_type, options;
   1.129 +    if (response_type == RESPONSE_TYPE_SOLICITED) {
   1.130 +      let token = this.readInt32();
   1.131 +      let error = this.readInt32();
   1.132 +
   1.133 +      options = this.mTokenRequestMap.get(token);
   1.134 +      if (!options) {
   1.135 +        if (DEBUG) {
   1.136 +          this.context.debug("Suspicious uninvited request found: " +
   1.137 +                             token + ". Ignored!");
   1.138 +        }
   1.139 +        return;
   1.140 +      }
   1.141 +
   1.142 +      this.mTokenRequestMap.delete(token);
   1.143 +      request_type = options.rilRequestType;
   1.144 +
   1.145 +      options.rilRequestError = error;
   1.146 +      if (DEBUG) {
   1.147 +        this.context.debug("Solicited response for request type " + request_type +
   1.148 +                           ", token " + token + ", error " + error);
   1.149 +      }
   1.150 +    } else if (response_type == RESPONSE_TYPE_UNSOLICITED) {
   1.151 +      request_type = this.readInt32();
   1.152 +      if (DEBUG) {
   1.153 +        this.context.debug("Unsolicited response for request type " + request_type);
   1.154 +      }
   1.155 +    } else {
   1.156 +      if (DEBUG) {
   1.157 +        this.context.debug("Unknown response type: " + response_type);
   1.158 +      }
   1.159 +      return;
   1.160 +    }
   1.161 +
   1.162 +    this.context.RIL.handleParcel(request_type, this.readAvailable, options);
   1.163 +  },
   1.164 +
   1.165 +  /**
   1.166 +   * Start a new outgoing parcel.
   1.167 +   *
   1.168 +   * @param type
   1.169 +   *        Integer specifying the request type.
   1.170 +   * @param options [optional]
   1.171 +   *        Object containing information about the request, e.g. the
   1.172 +   *        original main thread message object that led to the RIL request.
   1.173 +   */
   1.174 +  newParcel: function(type, options) {
   1.175 +    if (DEBUG) this.context.debug("New outgoing parcel of type " + type);
   1.176 +
   1.177 +    // We're going to leave room for the parcel size at the beginning.
   1.178 +    this.outgoingIndex = this.PARCEL_SIZE_SIZE;
   1.179 +    this.writeInt32(type);
   1.180 +    this.writeInt32(this.mToken);
   1.181 +
   1.182 +    if (!options) {
   1.183 +      options = {};
   1.184 +    }
   1.185 +    options.rilRequestType = type;
   1.186 +    options.rilRequestError = null;
   1.187 +    this.mTokenRequestMap.set(this.mToken, options);
   1.188 +    this.mToken++;
   1.189 +    return this.mToken;
   1.190 +  },
   1.191 +
   1.192 +  simpleRequest: function(type, options) {
   1.193 +    this.newParcel(type, options);
   1.194 +    this.sendParcel();
   1.195 +  },
   1.196 +
   1.197 +  onSendParcel: function(parcel) {
   1.198 +    postRILMessage(this.context.clientId, parcel);
   1.199 +  }
   1.200 +};
   1.201 +
   1.202 +(function() {
   1.203 +  let base = require("resource://gre/modules/workers/worker_buf.js").Buf;
   1.204 +  for (let p in base) {
   1.205 +    BufObject.prototype[p] = base[p];
   1.206 +  }
   1.207 +})();
   1.208 +
   1.209 +/**
   1.210 + * The RIL state machine.
   1.211 + *
   1.212 + * This object communicates with rild via parcels and with the main thread
   1.213 + * via post messages. It maintains state about the radio, ICC, calls, etc.
   1.214 + * and acts upon state changes accordingly.
   1.215 + */
   1.216 +function RilObject(aContext) {
   1.217 +  this.context = aContext;
   1.218 +
   1.219 +  this.currentCalls = {};
   1.220 +  this.currentConference = {state: null, participants: {}};
   1.221 +  this.currentDataCalls = {};
   1.222 +  this._pendingSentSmsMap = {};
   1.223 +  this.pendingNetworkType = {};
   1.224 +  this._receivedSmsCbPagesMap = {};
   1.225 +
   1.226 +  // Init properties that are only initialized once.
   1.227 +  this.v5Legacy = RILQUIRKS_V5_LEGACY;
   1.228 +  this.cellBroadcastDisabled = RIL_CELLBROADCAST_DISABLED;
   1.229 +  this.clirMode = RIL_CLIR_MODE;
   1.230 +}
   1.231 +RilObject.prototype = {
   1.232 +  context: null,
   1.233 +
   1.234 +  v5Legacy: null,
   1.235 +
   1.236 +  /**
   1.237 +   * Valid calls.
   1.238 +   */
   1.239 +  currentCalls: null,
   1.240 +
   1.241 +  /**
   1.242 +   * Existing conference call and its participants.
   1.243 +   */
   1.244 +  currentConference: null,
   1.245 +
   1.246 +  /**
   1.247 +   * Existing data calls.
   1.248 +   */
   1.249 +  currentDataCalls: null,
   1.250 +
   1.251 +  /**
   1.252 +   * Outgoing messages waiting for SMS-STATUS-REPORT.
   1.253 +   */
   1.254 +  _pendingSentSmsMap: null,
   1.255 +
   1.256 +  /**
   1.257 +   * Index of the RIL_PREFERRED_NETWORK_TYPE_TO_GECKO. Its value should be
   1.258 +   * preserved over rild reset.
   1.259 +   */
   1.260 +  preferredNetworkType: null,
   1.261 +
   1.262 +  /**
   1.263 +   * Marker object.
   1.264 +   */
   1.265 +  pendingNetworkType: null,
   1.266 +
   1.267 +  /**
   1.268 +   * Global Cell Broadcast switch.
   1.269 +   */
   1.270 +  cellBroadcastDisabled: false,
   1.271 +
   1.272 +  /**
   1.273 +   * Global CLIR mode settings.
   1.274 +   */
   1.275 +  clirMode: CLIR_DEFAULT,
   1.276 +
   1.277 +  /**
   1.278 +   * Parsed Cell Broadcast search lists.
   1.279 +   * cellBroadcastConfigs.MMI should be preserved over rild reset.
   1.280 +   */
   1.281 +  cellBroadcastConfigs: null,
   1.282 +  mergedCellBroadcastConfig: null,
   1.283 +
   1.284 +  _receivedSmsCbPagesMap: null,
   1.285 +
   1.286 +  initRILState: function() {
   1.287 +    /**
   1.288 +     * One of the RADIO_STATE_* constants.
   1.289 +     */
   1.290 +    this.radioState = GECKO_RADIOSTATE_UNAVAILABLE;
   1.291 +
   1.292 +    /**
   1.293 +     * True if we are on a CDMA phone.
   1.294 +     */
   1.295 +    this._isCdma = false;
   1.296 +
   1.297 +    /**
   1.298 +     * True if we are in emergency callback mode.
   1.299 +     */
   1.300 +    this._isInEmergencyCbMode = false;
   1.301 +
   1.302 +    /**
   1.303 +     * Set when radio is ready but radio tech is unknown. That is, we are
   1.304 +     * waiting for REQUEST_VOICE_RADIO_TECH
   1.305 +     */
   1.306 +    this._waitingRadioTech = false;
   1.307 +
   1.308 +    /**
   1.309 +     * ICC status. Keeps a reference of the data response to the
   1.310 +     * getICCStatus request.
   1.311 +     */
   1.312 +    this.iccStatus = null;
   1.313 +
   1.314 +    /**
   1.315 +     * Card state
   1.316 +     */
   1.317 +    this.cardState = GECKO_CARDSTATE_UNINITIALIZED;
   1.318 +
   1.319 +    /**
   1.320 +     * Strings
   1.321 +     */
   1.322 +    this.IMEI = null;
   1.323 +    this.IMEISV = null;
   1.324 +    this.ESN = null;
   1.325 +    this.MEID = null;
   1.326 +    this.SMSC = null;
   1.327 +
   1.328 +    /**
   1.329 +     * ICC information that is not exposed to Gaia.
   1.330 +     */
   1.331 +    this.iccInfoPrivate = {};
   1.332 +
   1.333 +    /**
   1.334 +     * ICC information, such as MSISDN, MCC, MNC, SPN...etc.
   1.335 +     */
   1.336 +    this.iccInfo = {};
   1.337 +
   1.338 +    /**
   1.339 +     * CDMA specific information. ex. CDMA Network ID, CDMA System ID... etc.
   1.340 +     */
   1.341 +    this.cdmaHome = null;
   1.342 +
   1.343 +    /**
   1.344 +     * Application identification for apps in ICC.
   1.345 +     */
   1.346 +    this.aid = null;
   1.347 +
   1.348 +    /**
   1.349 +     * Application type for apps in ICC.
   1.350 +     */
   1.351 +    this.appType = null;
   1.352 +
   1.353 +    this.networkSelectionMode = null;
   1.354 +
   1.355 +    this.voiceRegistrationState = {};
   1.356 +    this.dataRegistrationState = {};
   1.357 +
   1.358 +    /**
   1.359 +     * List of strings identifying the network operator.
   1.360 +     */
   1.361 +    this.operator = null;
   1.362 +
   1.363 +    /**
   1.364 +     * String containing the baseband version.
   1.365 +     */
   1.366 +    this.basebandVersion = null;
   1.367 +
   1.368 +    // Clean up this.currentCalls: rild might have restarted.
   1.369 +    for each (let currentCall in this.currentCalls) {
   1.370 +      delete this.currentCalls[currentCall.callIndex];
   1.371 +      this._handleDisconnectedCall(currentCall);
   1.372 +    }
   1.373 +
   1.374 +    // Deactivate this.currentDataCalls: rild might have restarted.
   1.375 +    for each (let datacall in this.currentDataCalls) {
   1.376 +      this.deactivateDataCall(datacall);
   1.377 +    }
   1.378 +
   1.379 +    // Don't clean up this._pendingSentSmsMap
   1.380 +    // because on rild restart: we may continue with the pending segments.
   1.381 +
   1.382 +    /**
   1.383 +     * Whether or not the multiple requests in requestNetworkInfo() are currently
   1.384 +     * being processed
   1.385 +     */
   1.386 +    this._processingNetworkInfo = false;
   1.387 +
   1.388 +    /**
   1.389 +     * Multiple requestNetworkInfo() in a row before finishing the first
   1.390 +     * request, hence we need to fire requestNetworkInfo() again after
   1.391 +     * gathering all necessary stuffs. This is to make sure that ril_worker
   1.392 +     * gets precise network information.
   1.393 +     */
   1.394 +    this._needRepollNetworkInfo = false;
   1.395 +
   1.396 +    /**
   1.397 +     * Pending messages to be send in batch from requestNetworkInfo()
   1.398 +     */
   1.399 +    this._pendingNetworkInfo = {rilMessageType: "networkinfochanged"};
   1.400 +
   1.401 +    /**
   1.402 +     * USSD session flag.
   1.403 +     * Only one USSD session may exist at a time, and the session is assumed
   1.404 +     * to exist until:
   1.405 +     *    a) There's a call to cancelUSSD()
   1.406 +     *    b) The implementation sends a UNSOLICITED_ON_USSD with a type code
   1.407 +     *       of "0" (USSD-Notify/no further action) or "2" (session terminated)
   1.408 +     */
   1.409 +    this._ussdSession = null;
   1.410 +
   1.411 +   /**
   1.412 +    * Regular expresion to parse MMI codes.
   1.413 +    */
   1.414 +    this._mmiRegExp = null;
   1.415 +
   1.416 +    /**
   1.417 +     * Cell Broadcast Search Lists.
   1.418 +     */
   1.419 +    let cbmmi = this.cellBroadcastConfigs && this.cellBroadcastConfigs.MMI;
   1.420 +    this.cellBroadcastConfigs = {
   1.421 +      MMI: cbmmi || null
   1.422 +    };
   1.423 +    this.mergedCellBroadcastConfig = null;
   1.424 +  },
   1.425 +
   1.426 +  /**
   1.427 +   * Parse an integer from a string, falling back to a default value
   1.428 +   * if the the provided value is not a string or does not contain a valid
   1.429 +   * number.
   1.430 +   *
   1.431 +   * @param string
   1.432 +   *        String to be parsed.
   1.433 +   * @param defaultValue [optional]
   1.434 +   *        Default value to be used.
   1.435 +   * @param radix [optional]
   1.436 +   *        A number that represents the numeral system to be used. Default 10.
   1.437 +   */
   1.438 +  parseInt: function(string, defaultValue, radix) {
   1.439 +    let number = parseInt(string, radix || 10);
   1.440 +    if (!isNaN(number)) {
   1.441 +      return number;
   1.442 +    }
   1.443 +    if (defaultValue === undefined) {
   1.444 +      defaultValue = null;
   1.445 +    }
   1.446 +    return defaultValue;
   1.447 +  },
   1.448 +
   1.449 +
   1.450 +  /**
   1.451 +   * Outgoing requests to the RIL. These can be triggered from the
   1.452 +   * main thread via messages that look like this:
   1.453 +   *
   1.454 +   *   {rilMessageType: "methodName",
   1.455 +   *    extra:          "parameters",
   1.456 +   *    go:             "here"}
   1.457 +   *
   1.458 +   * So if one of the following methods takes arguments, it takes only one,
   1.459 +   * an object, which then contains all of the parameters as attributes.
   1.460 +   * The "@param" documentation is to be interpreted accordingly.
   1.461 +   */
   1.462 +
   1.463 +  /**
   1.464 +   * Retrieve the ICC's status.
   1.465 +   */
   1.466 +  getICCStatus: function() {
   1.467 +    this.context.Buf.simpleRequest(REQUEST_GET_SIM_STATUS);
   1.468 +  },
   1.469 +
   1.470 +  /**
   1.471 +   * Helper function for unlocking ICC locks.
   1.472 +   */
   1.473 +  iccUnlockCardLock: function(options) {
   1.474 +    switch (options.lockType) {
   1.475 +      case GECKO_CARDLOCK_PIN:
   1.476 +        this.enterICCPIN(options);
   1.477 +        break;
   1.478 +      case GECKO_CARDLOCK_PIN2:
   1.479 +        this.enterICCPIN2(options);
   1.480 +        break;
   1.481 +      case GECKO_CARDLOCK_PUK:
   1.482 +        this.enterICCPUK(options);
   1.483 +        break;
   1.484 +      case GECKO_CARDLOCK_PUK2:
   1.485 +        this.enterICCPUK2(options);
   1.486 +        break;
   1.487 +      case GECKO_CARDLOCK_NCK:
   1.488 +      case GECKO_CARDLOCK_NCK1:
   1.489 +      case GECKO_CARDLOCK_NCK2:
   1.490 +      case GECKO_CARDLOCK_HNCK:
   1.491 +      case GECKO_CARDLOCK_CCK:
   1.492 +      case GECKO_CARDLOCK_SPCK:
   1.493 +      case GECKO_CARDLOCK_RCCK: // Fall through.
   1.494 +      case GECKO_CARDLOCK_RSPCK: {
   1.495 +        let type = GECKO_PERSO_LOCK_TO_CARD_PERSO_LOCK[options.lockType];
   1.496 +        this.enterDepersonalization(type, options.pin, options);
   1.497 +        break;
   1.498 +      }
   1.499 +      case GECKO_CARDLOCK_NCK_PUK:
   1.500 +      case GECKO_CARDLOCK_NCK1_PUK:
   1.501 +      case GECKO_CARDLOCK_NCK2_PUK:
   1.502 +      case GECKO_CARDLOCK_HNCK_PUK:
   1.503 +      case GECKO_CARDLOCK_CCK_PUK:
   1.504 +      case GECKO_CARDLOCK_SPCK_PUK:
   1.505 +      case GECKO_CARDLOCK_RCCK_PUK: // Fall through.
   1.506 +      case GECKO_CARDLOCK_RSPCK_PUK: {
   1.507 +        let type = GECKO_PERSO_LOCK_TO_CARD_PERSO_LOCK[options.lockType];
   1.508 +        this.enterDepersonalization(type, options.puk, options);
   1.509 +        break;
   1.510 +      }
   1.511 +      default:
   1.512 +        options.errorMsg = "Unsupported Card Lock.";
   1.513 +        options.success = false;
   1.514 +        this.sendChromeMessage(options);
   1.515 +    }
   1.516 +  },
   1.517 +
   1.518 +  /**
   1.519 +   * Enter a PIN to unlock the ICC.
   1.520 +   *
   1.521 +   * @param pin
   1.522 +   *        String containing the PIN.
   1.523 +   * @param [optional] aid
   1.524 +   *        AID value.
   1.525 +   */
   1.526 +  enterICCPIN: function(options) {
   1.527 +    let Buf = this.context.Buf;
   1.528 +    Buf.newParcel(REQUEST_ENTER_SIM_PIN, options);
   1.529 +    Buf.writeInt32(this.v5Legacy ? 1 : 2);
   1.530 +    Buf.writeString(options.pin);
   1.531 +    if (!this.v5Legacy) {
   1.532 +      Buf.writeString(options.aid || this.aid);
   1.533 +    }
   1.534 +    Buf.sendParcel();
   1.535 +  },
   1.536 +
   1.537 +  /**
   1.538 +   * Enter a PIN2 to unlock the ICC.
   1.539 +   *
   1.540 +   * @param pin
   1.541 +   *        String containing the PIN2.
   1.542 +   * @param [optional] aid
   1.543 +   *        AID value.
   1.544 +   */
   1.545 +  enterICCPIN2: function(options) {
   1.546 +    let Buf = this.context.Buf;
   1.547 +    Buf.newParcel(REQUEST_ENTER_SIM_PIN2, options);
   1.548 +    Buf.writeInt32(this.v5Legacy ? 1 : 2);
   1.549 +    Buf.writeString(options.pin);
   1.550 +    if (!this.v5Legacy) {
   1.551 +      Buf.writeString(options.aid || this.aid);
   1.552 +    }
   1.553 +    Buf.sendParcel();
   1.554 +  },
   1.555 +
   1.556 +  /**
   1.557 +   * Requests a network personalization be deactivated.
   1.558 +   *
   1.559 +   * @param type
   1.560 +   *        Integer indicating the network personalization be deactivated.
   1.561 +   * @param password
   1.562 +   *        String containing the password.
   1.563 +   */
   1.564 +  enterDepersonalization: function(type, password, options) {
   1.565 +    let Buf = this.context.Buf;
   1.566 +    Buf.newParcel(REQUEST_ENTER_NETWORK_DEPERSONALIZATION_CODE, options);
   1.567 +    Buf.writeInt32(type);
   1.568 +    Buf.writeString(password);
   1.569 +    Buf.sendParcel();
   1.570 +  },
   1.571 +
   1.572 +  /**
   1.573 +   * Helper function for changing ICC locks.
   1.574 +   */
   1.575 +  iccSetCardLock: function(options) {
   1.576 +    if (options.newPin !== undefined) { // Change PIN lock.
   1.577 +      switch (options.lockType) {
   1.578 +        case GECKO_CARDLOCK_PIN:
   1.579 +          this.changeICCPIN(options);
   1.580 +          break;
   1.581 +        case GECKO_CARDLOCK_PIN2:
   1.582 +          this.changeICCPIN2(options);
   1.583 +          break;
   1.584 +        default:
   1.585 +          options.errorMsg = "Unsupported Card Lock.";
   1.586 +          options.success = false;
   1.587 +          this.sendChromeMessage(options);
   1.588 +      }
   1.589 +    } else { // Enable/Disable lock.
   1.590 +      switch (options.lockType) {
   1.591 +        case GECKO_CARDLOCK_PIN:
   1.592 +          options.facility = ICC_CB_FACILITY_SIM;
   1.593 +          options.password = options.pin;
   1.594 +          break;
   1.595 +        case GECKO_CARDLOCK_FDN:
   1.596 +          options.facility = ICC_CB_FACILITY_FDN;
   1.597 +          options.password = options.pin2;
   1.598 +          break;
   1.599 +        default:
   1.600 +          options.errorMsg = "Unsupported Card Lock.";
   1.601 +          options.success = false;
   1.602 +          this.sendChromeMessage(options);
   1.603 +          return;
   1.604 +      }
   1.605 +      options.enabled = options.enabled;
   1.606 +      options.serviceClass = ICC_SERVICE_CLASS_VOICE |
   1.607 +                             ICC_SERVICE_CLASS_DATA  |
   1.608 +                             ICC_SERVICE_CLASS_FAX;
   1.609 +      this.setICCFacilityLock(options);
   1.610 +    }
   1.611 +  },
   1.612 +
   1.613 +  /**
   1.614 +   * Change the current ICC PIN number.
   1.615 +   *
   1.616 +   * @param pin
   1.617 +   *        String containing the old PIN value
   1.618 +   * @param newPin
   1.619 +   *        String containing the new PIN value
   1.620 +   * @param [optional] aid
   1.621 +   *        AID value.
   1.622 +   */
   1.623 +  changeICCPIN: function(options) {
   1.624 +    let Buf = this.context.Buf;
   1.625 +    Buf.newParcel(REQUEST_CHANGE_SIM_PIN, options);
   1.626 +    Buf.writeInt32(this.v5Legacy ? 2 : 3);
   1.627 +    Buf.writeString(options.pin);
   1.628 +    Buf.writeString(options.newPin);
   1.629 +    if (!this.v5Legacy) {
   1.630 +      Buf.writeString(options.aid || this.aid);
   1.631 +    }
   1.632 +    Buf.sendParcel();
   1.633 +  },
   1.634 +
   1.635 +  /**
   1.636 +   * Change the current ICC PIN2 number.
   1.637 +   *
   1.638 +   * @param pin
   1.639 +   *        String containing the old PIN2 value
   1.640 +   * @param newPin
   1.641 +   *        String containing the new PIN2 value
   1.642 +   * @param [optional] aid
   1.643 +   *        AID value.
   1.644 +   */
   1.645 +  changeICCPIN2: function(options) {
   1.646 +    let Buf = this.context.Buf;
   1.647 +    Buf.newParcel(REQUEST_CHANGE_SIM_PIN2, options);
   1.648 +    Buf.writeInt32(this.v5Legacy ? 2 : 3);
   1.649 +    Buf.writeString(options.pin);
   1.650 +    Buf.writeString(options.newPin);
   1.651 +    if (!this.v5Legacy) {
   1.652 +      Buf.writeString(options.aid || this.aid);
   1.653 +    }
   1.654 +    Buf.sendParcel();
   1.655 +  },
   1.656 +  /**
   1.657 +   * Supplies ICC PUK and a new PIN to unlock the ICC.
   1.658 +   *
   1.659 +   * @param puk
   1.660 +   *        String containing the PUK value.
   1.661 +   * @param newPin
   1.662 +   *        String containing the new PIN value.
   1.663 +   * @param [optional] aid
   1.664 +   *        AID value.
   1.665 +   */
   1.666 +   enterICCPUK: function(options) {
   1.667 +     let Buf = this.context.Buf;
   1.668 +     Buf.newParcel(REQUEST_ENTER_SIM_PUK, options);
   1.669 +     Buf.writeInt32(this.v5Legacy ? 2 : 3);
   1.670 +     Buf.writeString(options.puk);
   1.671 +     Buf.writeString(options.newPin);
   1.672 +     if (!this.v5Legacy) {
   1.673 +       Buf.writeString(options.aid || this.aid);
   1.674 +     }
   1.675 +     Buf.sendParcel();
   1.676 +   },
   1.677 +
   1.678 +  /**
   1.679 +   * Supplies ICC PUK2 and a new PIN2 to unlock the ICC.
   1.680 +   *
   1.681 +   * @param puk
   1.682 +   *        String containing the PUK2 value.
   1.683 +   * @param newPin
   1.684 +   *        String containing the new PIN2 value.
   1.685 +   * @param [optional] aid
   1.686 +   *        AID value.
   1.687 +   */
   1.688 +   enterICCPUK2: function(options) {
   1.689 +     let Buf = this.context.Buf;
   1.690 +     Buf.newParcel(REQUEST_ENTER_SIM_PUK2, options);
   1.691 +     Buf.writeInt32(this.v5Legacy ? 2 : 3);
   1.692 +     Buf.writeString(options.puk);
   1.693 +     Buf.writeString(options.newPin);
   1.694 +     if (!this.v5Legacy) {
   1.695 +       Buf.writeString(options.aid || this.aid);
   1.696 +     }
   1.697 +     Buf.sendParcel();
   1.698 +   },
   1.699 +
   1.700 +  /**
   1.701 +   * Helper function for fetching the state of ICC locks.
   1.702 +   */
   1.703 +  iccGetCardLockState: function(options) {
   1.704 +    switch (options.lockType) {
   1.705 +      case GECKO_CARDLOCK_PIN:
   1.706 +        options.facility = ICC_CB_FACILITY_SIM;
   1.707 +        break;
   1.708 +      case GECKO_CARDLOCK_FDN:
   1.709 +        options.facility = ICC_CB_FACILITY_FDN;
   1.710 +        break;
   1.711 +      default:
   1.712 +        options.errorMsg = "Unsupported Card Lock.";
   1.713 +        options.success = false;
   1.714 +        this.sendChromeMessage(options);
   1.715 +        return;
   1.716 +    }
   1.717 +
   1.718 +    options.password = ""; // For query no need to provide pin.
   1.719 +    options.serviceClass = ICC_SERVICE_CLASS_VOICE |
   1.720 +                           ICC_SERVICE_CLASS_DATA  |
   1.721 +                           ICC_SERVICE_CLASS_FAX;
   1.722 +    this.queryICCFacilityLock(options);
   1.723 +  },
   1.724 +
   1.725 +  /**
   1.726 +   * Helper function for fetching the number of unlock retries of ICC locks.
   1.727 +   *
   1.728 +   * We only query the retry count when we're on the emulator. The phones do
   1.729 +   * not support the request id and their rild doesn't return an error.
   1.730 +   */
   1.731 +  iccGetCardLockRetryCount: function(options) {
   1.732 +    var selCode = {
   1.733 +      pin: ICC_SEL_CODE_SIM_PIN,
   1.734 +      puk: ICC_SEL_CODE_SIM_PUK,
   1.735 +      pin2: ICC_SEL_CODE_SIM_PIN2,
   1.736 +      puk2: ICC_SEL_CODE_SIM_PUK2,
   1.737 +      nck: ICC_SEL_CODE_PH_NET_PIN,
   1.738 +      cck: ICC_SEL_CODE_PH_CORP_PIN,
   1.739 +      spck: ICC_SEL_CODE_PH_SP_PIN
   1.740 +    };
   1.741 +
   1.742 +    if (typeof(selCode[options.lockType]) === 'undefined') {
   1.743 +      /* unknown lock type */
   1.744 +      options.errorMsg = GECKO_ERROR_GENERIC_FAILURE;
   1.745 +      options.success = false;
   1.746 +      this.sendChromeMessage(options);
   1.747 +      return;
   1.748 +    }
   1.749 +
   1.750 +    if (RILQUIRKS_HAVE_QUERY_ICC_LOCK_RETRY_COUNT) {
   1.751 +      /* Only the emulator supports this request, ... */
   1.752 +      options.selCode = selCode[options.lockType];
   1.753 +      this.queryICCLockRetryCount(options);
   1.754 +    } else {
   1.755 +      /* ... while the phones do not. */
   1.756 +      options.errorMsg = GECKO_ERROR_REQUEST_NOT_SUPPORTED;
   1.757 +      options.success = false;
   1.758 +      this.sendChromeMessage(options);
   1.759 +    }
   1.760 +  },
   1.761 +
   1.762 +  /**
   1.763 +   * Query ICC lock retry count.
   1.764 +   *
   1.765 +   * @param selCode
   1.766 +   *        One of ICC_SEL_CODE_*.
   1.767 +   * @param serviceClass
   1.768 +   *        One of ICC_SERVICE_CLASS_*.
   1.769 +   */
   1.770 +  queryICCLockRetryCount: function(options) {
   1.771 +    let Buf = this.context.Buf;
   1.772 +    Buf.newParcel(REQUEST_GET_UNLOCK_RETRY_COUNT, options);
   1.773 +    Buf.writeInt32(1);
   1.774 +    Buf.writeString(options.selCode);
   1.775 +    Buf.sendParcel();
   1.776 +  },
   1.777 +
   1.778 +  /**
   1.779 +   * Query ICC facility lock.
   1.780 +   *
   1.781 +   * @param facility
   1.782 +   *        One of ICC_CB_FACILITY_*.
   1.783 +   * @param password
   1.784 +   *        Password for the facility, or "" if not required.
   1.785 +   * @param serviceClass
   1.786 +   *        One of ICC_SERVICE_CLASS_*.
   1.787 +   * @param [optional] aid
   1.788 +   *        AID value.
   1.789 +   */
   1.790 +  queryICCFacilityLock: function(options) {
   1.791 +    let Buf = this.context.Buf;
   1.792 +    Buf.newParcel(REQUEST_QUERY_FACILITY_LOCK, options);
   1.793 +    Buf.writeInt32(this.v5Legacy ? 3 : 4);
   1.794 +    Buf.writeString(options.facility);
   1.795 +    Buf.writeString(options.password);
   1.796 +    Buf.writeString(options.serviceClass.toString());
   1.797 +    if (!this.v5Legacy) {
   1.798 +      Buf.writeString(options.aid || this.aid);
   1.799 +    }
   1.800 +    Buf.sendParcel();
   1.801 +  },
   1.802 +
   1.803 +  /**
   1.804 +   * Set ICC facility lock.
   1.805 +   *
   1.806 +   * @param facility
   1.807 +   *        One of ICC_CB_FACILITY_*.
   1.808 +   * @param enabled
   1.809 +   *        true to enable, false to disable.
   1.810 +   * @param password
   1.811 +   *        Password for the facility, or "" if not required.
   1.812 +   * @param serviceClass
   1.813 +   *        One of ICC_SERVICE_CLASS_*.
   1.814 +   * @param [optional] aid
   1.815 +   *        AID value.
   1.816 +   */
   1.817 +  setICCFacilityLock: function(options) {
   1.818 +    let Buf = this.context.Buf;
   1.819 +    Buf.newParcel(REQUEST_SET_FACILITY_LOCK, options);
   1.820 +    Buf.writeInt32(this.v5Legacy ? 4 : 5);
   1.821 +    Buf.writeString(options.facility);
   1.822 +    Buf.writeString(options.enabled ? "1" : "0");
   1.823 +    Buf.writeString(options.password);
   1.824 +    Buf.writeString(options.serviceClass.toString());
   1.825 +    if (!this.v5Legacy) {
   1.826 +      Buf.writeString(options.aid || this.aid);
   1.827 +    }
   1.828 +    Buf.sendParcel();
   1.829 +  },
   1.830 +
   1.831 +  /**
   1.832 +   *  Request an ICC I/O operation.
   1.833 +   *
   1.834 +   *  See TS 27.007 "restricted SIM" operation, "AT Command +CRSM".
   1.835 +   *  The sequence is in the same order as how libril reads this parcel,
   1.836 +   *  see the struct RIL_SIM_IO_v5 or RIL_SIM_IO_v6 defined in ril.h
   1.837 +   *
   1.838 +   *  @param command
   1.839 +   *         The I/O command, one of the ICC_COMMAND_* constants.
   1.840 +   *  @param fileId
   1.841 +   *         The file to operate on, one of the ICC_EF_* constants.
   1.842 +   *  @param pathId
   1.843 +   *         String type, check the 'pathid' parameter from TS 27.007 +CRSM.
   1.844 +   *  @param p1, p2, p3
   1.845 +   *         Arbitrary integer parameters for the command.
   1.846 +   *  @param [optional] dataWriter
   1.847 +   *         The function for writing string parameter for the ICC_COMMAND_UPDATE_RECORD.
   1.848 +   *  @param [optional] pin2
   1.849 +   *         String containing the PIN2.
   1.850 +   *  @param [optional] aid
   1.851 +   *         AID value.
   1.852 +   */
   1.853 +  iccIO: function(options) {
   1.854 +    let Buf = this.context.Buf;
   1.855 +    Buf.newParcel(REQUEST_SIM_IO, options);
   1.856 +    Buf.writeInt32(options.command);
   1.857 +    Buf.writeInt32(options.fileId);
   1.858 +    Buf.writeString(options.pathId);
   1.859 +    Buf.writeInt32(options.p1);
   1.860 +    Buf.writeInt32(options.p2);
   1.861 +    Buf.writeInt32(options.p3);
   1.862 +
   1.863 +    // Write data.
   1.864 +    if (options.command == ICC_COMMAND_UPDATE_RECORD &&
   1.865 +        options.dataWriter) {
   1.866 +      options.dataWriter(options.p3);
   1.867 +    } else {
   1.868 +      Buf.writeString(null);
   1.869 +    }
   1.870 +
   1.871 +    // Write pin2.
   1.872 +    if (options.command == ICC_COMMAND_UPDATE_RECORD &&
   1.873 +        options.pin2) {
   1.874 +      Buf.writeString(options.pin2);
   1.875 +    } else {
   1.876 +      Buf.writeString(null);
   1.877 +    }
   1.878 +
   1.879 +    if (!this.v5Legacy) {
   1.880 +      Buf.writeString(options.aid || this.aid);
   1.881 +    }
   1.882 +    Buf.sendParcel();
   1.883 +  },
   1.884 +
   1.885 +  /**
   1.886 +   * Get IMSI.
   1.887 +   *
   1.888 +   * @param [optional] aid
   1.889 +   *        AID value.
   1.890 +   */
   1.891 +  getIMSI: function(aid) {
   1.892 +    let Buf = this.context.Buf;
   1.893 +    if (this.v5Legacy) {
   1.894 +      Buf.simpleRequest(REQUEST_GET_IMSI);
   1.895 +      return;
   1.896 +    }
   1.897 +    Buf.newParcel(REQUEST_GET_IMSI);
   1.898 +    Buf.writeInt32(1);
   1.899 +    Buf.writeString(aid || this.aid);
   1.900 +    Buf.sendParcel();
   1.901 +  },
   1.902 +
   1.903 +  /**
   1.904 +   * Read UICC Phonebook contacts.
   1.905 +   *
   1.906 +   * @param contactType
   1.907 +   *        "adn" or "fdn".
   1.908 +   * @param requestId
   1.909 +   *        Request id from RadioInterfaceLayer.
   1.910 +   */
   1.911 +  readICCContacts: function(options) {
   1.912 +    if (!this.appType) {
   1.913 +      options.errorMsg = CONTACT_ERR_REQUEST_NOT_SUPPORTED;
   1.914 +      this.sendChromeMessage(options);
   1.915 +      return;
   1.916 +    }
   1.917 +
   1.918 +    this.context.ICCContactHelper.readICCContacts(
   1.919 +      this.appType,
   1.920 +      options.contactType,
   1.921 +      function onsuccess(contacts) {
   1.922 +        for (let i = 0; i < contacts.length; i++) {
   1.923 +          let contact = contacts[i];
   1.924 +          let pbrIndex = contact.pbrIndex || 0;
   1.925 +          let recordIndex = pbrIndex * ICC_MAX_LINEAR_FIXED_RECORDS + contact.recordId;
   1.926 +          contact.contactId = this.iccInfo.iccid + recordIndex;
   1.927 +        }
   1.928 +        // Reuse 'options' to get 'requestId' and 'contactType'.
   1.929 +        options.contacts = contacts;
   1.930 +        this.sendChromeMessage(options);
   1.931 +      }.bind(this),
   1.932 +      function onerror(errorMsg) {
   1.933 +        options.errorMsg = errorMsg;
   1.934 +        this.sendChromeMessage(options);
   1.935 +      }.bind(this));
   1.936 +  },
   1.937 +
   1.938 +  /**
   1.939 +   * Update UICC Phonebook.
   1.940 +   *
   1.941 +   * @param contactType   "adn" or "fdn".
   1.942 +   * @param contact       The contact will be updated.
   1.943 +   * @param pin2          PIN2 is required for updating FDN.
   1.944 +   * @param requestId     Request id from RadioInterfaceLayer.
   1.945 +   */
   1.946 +  updateICCContact: function(options) {
   1.947 +    let onsuccess = function onsuccess() {
   1.948 +      let recordIndex =
   1.949 +        contact.pbrIndex * ICC_MAX_LINEAR_FIXED_RECORDS + contact.recordId;
   1.950 +      contact.contactId = this.iccInfo.iccid + recordIndex;
   1.951 +      // Reuse 'options' to get 'requestId' and 'contactType'.
   1.952 +      this.sendChromeMessage(options);
   1.953 +    }.bind(this);
   1.954 +
   1.955 +    let onerror = function onerror(errorMsg) {
   1.956 +      options.errorMsg = errorMsg;
   1.957 +      this.sendChromeMessage(options);
   1.958 +    }.bind(this);
   1.959 +
   1.960 +    if (!this.appType || !options.contact) {
   1.961 +      onerror(CONTACT_ERR_REQUEST_NOT_SUPPORTED );
   1.962 +      return;
   1.963 +    }
   1.964 +
   1.965 +    let contact = options.contact;
   1.966 +    let iccid = this.iccInfo.iccid;
   1.967 +    let isValidRecordId = false;
   1.968 +    if (typeof contact.contactId === "string" &&
   1.969 +        contact.contactId.startsWith(iccid)) {
   1.970 +      let recordIndex = contact.contactId.substring(iccid.length);
   1.971 +      contact.pbrIndex = Math.floor(recordIndex / ICC_MAX_LINEAR_FIXED_RECORDS);
   1.972 +      contact.recordId = recordIndex % ICC_MAX_LINEAR_FIXED_RECORDS;
   1.973 +      isValidRecordId = contact.recordId > 0 && contact.recordId < 0xff;
   1.974 +    }
   1.975 +
   1.976 +    if (DEBUG) {
   1.977 +      this.context.debug("Update ICC Contact " + JSON.stringify(contact));
   1.978 +    }
   1.979 +
   1.980 +    let ICCContactHelper = this.context.ICCContactHelper;
   1.981 +    // If contact has 'recordId' property, updates corresponding record.
   1.982 +    // If not, inserts the contact into a free record.
   1.983 +    if (isValidRecordId) {
   1.984 +      ICCContactHelper.updateICCContact(
   1.985 +        this.appType, options.contactType, contact, options.pin2, onsuccess, onerror);
   1.986 +    } else {
   1.987 +      ICCContactHelper.addICCContact(
   1.988 +        this.appType, options.contactType, contact, options.pin2, onsuccess, onerror);
   1.989 +    }
   1.990 +  },
   1.991 +
   1.992 +  /**
   1.993 +   * Request the phone's radio to be enabled or disabled.
   1.994 +   *
   1.995 +   * @param enabled
   1.996 +   *        Boolean indicating the desired state.
   1.997 +   */
   1.998 +  setRadioEnabled: function(options) {
   1.999 +    let Buf = this.context.Buf;
  1.1000 +    Buf.newParcel(REQUEST_RADIO_POWER, options);
  1.1001 +    Buf.writeInt32(1);
  1.1002 +    Buf.writeInt32(options.enabled ? 1 : 0);
  1.1003 +    Buf.sendParcel();
  1.1004 +  },
  1.1005 +
  1.1006 +  /**
  1.1007 +   * Query call waiting status via MMI.
  1.1008 +   */
  1.1009 +  _handleQueryMMICallWaiting: function(options) {
  1.1010 +    let Buf = this.context.Buf;
  1.1011 +
  1.1012 +    function callback(options) {
  1.1013 +      options.length = Buf.readInt32();
  1.1014 +      options.enabled = (Buf.readInt32() === 1);
  1.1015 +      let services = Buf.readInt32();
  1.1016 +      if (options.enabled) {
  1.1017 +        options.statusMessage = MMI_SM_KS_SERVICE_ENABLED_FOR;
  1.1018 +        let serviceClass = [];
  1.1019 +        for (let serviceClassMask = 1;
  1.1020 +             serviceClassMask <= ICC_SERVICE_CLASS_MAX;
  1.1021 +             serviceClassMask <<= 1) {
  1.1022 +          if ((serviceClassMask & services) !== 0) {
  1.1023 +            serviceClass.push(MMI_KS_SERVICE_CLASS_MAPPING[serviceClassMask]);
  1.1024 +          }
  1.1025 +        }
  1.1026 +        options.additionalInformation = serviceClass;
  1.1027 +      } else {
  1.1028 +        options.statusMessage = MMI_SM_KS_SERVICE_DISABLED;
  1.1029 +      }
  1.1030 +
  1.1031 +      // Prevent DataCloneError when sending chrome messages.
  1.1032 +      delete options.callback;
  1.1033 +      this.sendChromeMessage(options);
  1.1034 +    }
  1.1035 +
  1.1036 +    options.callback = callback;
  1.1037 +    this.queryCallWaiting(options);
  1.1038 +  },
  1.1039 +
  1.1040 +  /**
  1.1041 +   * Set call waiting status via MMI.
  1.1042 +   */
  1.1043 +  _handleSetMMICallWaiting: function(options) {
  1.1044 +    function callback(options) {
  1.1045 +      if (options.enabled) {
  1.1046 +        options.statusMessage = MMI_SM_KS_SERVICE_ENABLED;
  1.1047 +      } else {
  1.1048 +        options.statusMessage = MMI_SM_KS_SERVICE_DISABLED;
  1.1049 +      }
  1.1050 +
  1.1051 +      // Prevent DataCloneError when sending chrome messages.
  1.1052 +      delete options.callback;
  1.1053 +      this.sendChromeMessage(options);
  1.1054 +    }
  1.1055 +
  1.1056 +    options.callback = callback;
  1.1057 +    this.setCallWaiting(options);
  1.1058 +  },
  1.1059 +
  1.1060 +  /**
  1.1061 +   * Query call waiting status.
  1.1062 +   *
  1.1063 +   */
  1.1064 +  queryCallWaiting: function(options) {
  1.1065 +    let Buf = this.context.Buf;
  1.1066 +    Buf.newParcel(REQUEST_QUERY_CALL_WAITING, options);
  1.1067 +    Buf.writeInt32(1);
  1.1068 +    // As per 3GPP TS 24.083, section 1.6 UE doesn't need to send service
  1.1069 +    // class parameter in call waiting interrogation  to network
  1.1070 +    Buf.writeInt32(ICC_SERVICE_CLASS_NONE);
  1.1071 +    Buf.sendParcel();
  1.1072 +  },
  1.1073 +
  1.1074 +  /**
  1.1075 +   * Set call waiting status.
  1.1076 +   *
  1.1077 +   * @param on
  1.1078 +   *        Boolean indicating the desired waiting status.
  1.1079 +   */
  1.1080 +  setCallWaiting: function(options) {
  1.1081 +    let Buf = this.context.Buf;
  1.1082 +    Buf.newParcel(REQUEST_SET_CALL_WAITING, options);
  1.1083 +    Buf.writeInt32(2);
  1.1084 +    Buf.writeInt32(options.enabled ? 1 : 0);
  1.1085 +    Buf.writeInt32(options.serviceClass !== undefined ?
  1.1086 +                    options.serviceClass : ICC_SERVICE_CLASS_VOICE);
  1.1087 +    Buf.sendParcel();
  1.1088 +  },
  1.1089 +
  1.1090 +  /**
  1.1091 +   * Queries current CLIP status.
  1.1092 +   *
  1.1093 +   * (MMI request for code "*#30#")
  1.1094 +   *
  1.1095 +   */
  1.1096 +  queryCLIP: function(options) {
  1.1097 +    this.context.Buf.simpleRequest(REQUEST_QUERY_CLIP, options);
  1.1098 +  },
  1.1099 +
  1.1100 +  /**
  1.1101 +   * Queries current CLIR status.
  1.1102 +   *
  1.1103 +   */
  1.1104 +  getCLIR: function(options) {
  1.1105 +    this.context.Buf.simpleRequest(REQUEST_GET_CLIR, options);
  1.1106 +  },
  1.1107 +
  1.1108 +  /**
  1.1109 +   * Enables or disables the presentation of the calling line identity (CLI) to
  1.1110 +   * the called party when originating a call.
  1.1111 +   *
  1.1112 +   * @param options.clirMode
  1.1113 +   *        Is one of the CLIR_* constants in
  1.1114 +   *        nsIDOMMozMobileConnection interface.
  1.1115 +   */
  1.1116 +  setCLIR: function(options) {
  1.1117 +    if (options) {
  1.1118 +      this.clirMode = options.clirMode;
  1.1119 +    }
  1.1120 +    let Buf = this.context.Buf;
  1.1121 +    Buf.newParcel(REQUEST_SET_CLIR, options);
  1.1122 +    Buf.writeInt32(1);
  1.1123 +    Buf.writeInt32(this.clirMode);
  1.1124 +    Buf.sendParcel();
  1.1125 +  },
  1.1126 +
  1.1127 +  /**
  1.1128 +   * Set screen state.
  1.1129 +   *
  1.1130 +   * @param on
  1.1131 +   *        Boolean indicating whether the screen should be on or off.
  1.1132 +   */
  1.1133 +  setScreenState: function(options) {
  1.1134 +    let Buf = this.context.Buf;
  1.1135 +    Buf.newParcel(REQUEST_SCREEN_STATE);
  1.1136 +    Buf.writeInt32(1);
  1.1137 +    Buf.writeInt32(options.on ? 1 : 0);
  1.1138 +    Buf.sendParcel();
  1.1139 +  },
  1.1140 +
  1.1141 +  getVoiceRegistrationState: function() {
  1.1142 +    this.context.Buf.simpleRequest(REQUEST_VOICE_REGISTRATION_STATE);
  1.1143 +  },
  1.1144 +
  1.1145 +  getVoiceRadioTechnology: function() {
  1.1146 +    this.context.Buf.simpleRequest(REQUEST_VOICE_RADIO_TECH);
  1.1147 +  },
  1.1148 +
  1.1149 +  getDataRegistrationState: function() {
  1.1150 +    this.context.Buf.simpleRequest(REQUEST_DATA_REGISTRATION_STATE);
  1.1151 +  },
  1.1152 +
  1.1153 +  getOperator: function() {
  1.1154 +    this.context.Buf.simpleRequest(REQUEST_OPERATOR);
  1.1155 +  },
  1.1156 +
  1.1157 +  /**
  1.1158 +   * Set the preferred network type.
  1.1159 +   *
  1.1160 +   * @param options An object contains a valid index of
  1.1161 +   *                RIL_PREFERRED_NETWORK_TYPE_TO_GECKO as its `networkType`
  1.1162 +   *                attribute, or undefined to set current preferred network
  1.1163 +   *                type.
  1.1164 +   */
  1.1165 +  setPreferredNetworkType: function(options) {
  1.1166 +    if (options) {
  1.1167 +      this.preferredNetworkType = options.networkType;
  1.1168 +    }
  1.1169 +    if (this.preferredNetworkType == null) {
  1.1170 +      return;
  1.1171 +    }
  1.1172 +
  1.1173 +    let Buf = this.context.Buf;
  1.1174 +    Buf.newParcel(REQUEST_SET_PREFERRED_NETWORK_TYPE, options);
  1.1175 +    Buf.writeInt32(1);
  1.1176 +    Buf.writeInt32(this.preferredNetworkType);
  1.1177 +    Buf.sendParcel();
  1.1178 +  },
  1.1179 +
  1.1180 +  /**
  1.1181 +   * Get the preferred network type.
  1.1182 +   */
  1.1183 +  getPreferredNetworkType: function(options) {
  1.1184 +    this.context.Buf.simpleRequest(REQUEST_GET_PREFERRED_NETWORK_TYPE, options);
  1.1185 +  },
  1.1186 +
  1.1187 +  /**
  1.1188 +   * Request various states about the network.
  1.1189 +   */
  1.1190 +  requestNetworkInfo: function() {
  1.1191 +    if (this._processingNetworkInfo) {
  1.1192 +      if (DEBUG) {
  1.1193 +        this.context.debug("Network info requested, but we're already " +
  1.1194 +                           "requesting network info.");
  1.1195 +      }
  1.1196 +      this._needRepollNetworkInfo = true;
  1.1197 +      return;
  1.1198 +    }
  1.1199 +
  1.1200 +    if (DEBUG) this.context.debug("Requesting network info");
  1.1201 +
  1.1202 +    this._processingNetworkInfo = true;
  1.1203 +    this.getVoiceRegistrationState();
  1.1204 +    this.getDataRegistrationState(); //TODO only GSM
  1.1205 +    this.getOperator();
  1.1206 +    this.getNetworkSelectionMode();
  1.1207 +    this.getSignalStrength();
  1.1208 +  },
  1.1209 +
  1.1210 +  /**
  1.1211 +   * Get the available networks
  1.1212 +   */
  1.1213 +  getAvailableNetworks: function(options) {
  1.1214 +    if (DEBUG) this.context.debug("Getting available networks");
  1.1215 +    let Buf = this.context.Buf;
  1.1216 +    Buf.newParcel(REQUEST_QUERY_AVAILABLE_NETWORKS, options);
  1.1217 +    Buf.sendParcel();
  1.1218 +  },
  1.1219 +
  1.1220 +  /**
  1.1221 +   * Request the radio's network selection mode
  1.1222 +   */
  1.1223 +  getNetworkSelectionMode: function() {
  1.1224 +    if (DEBUG) this.context.debug("Getting network selection mode");
  1.1225 +    this.context.Buf.simpleRequest(REQUEST_QUERY_NETWORK_SELECTION_MODE);
  1.1226 +  },
  1.1227 +
  1.1228 +  /**
  1.1229 +   * Tell the radio to automatically choose a voice/data network
  1.1230 +   */
  1.1231 +  selectNetworkAuto: function(options) {
  1.1232 +    if (DEBUG) this.context.debug("Setting automatic network selection");
  1.1233 +    this.context.Buf.simpleRequest(REQUEST_SET_NETWORK_SELECTION_AUTOMATIC, options);
  1.1234 +  },
  1.1235 +
  1.1236 +  /**
  1.1237 +   * Set the roaming preference mode
  1.1238 +   */
  1.1239 +  setRoamingPreference: function(options) {
  1.1240 +    let roamingMode = CDMA_ROAMING_PREFERENCE_TO_GECKO.indexOf(options.mode);
  1.1241 +
  1.1242 +    if (roamingMode === -1) {
  1.1243 +      options.errorMsg = GECKO_ERROR_INVALID_PARAMETER;
  1.1244 +      this.sendChromeMessage(options);
  1.1245 +      return;
  1.1246 +    }
  1.1247 +
  1.1248 +    let Buf = this.context.Buf;
  1.1249 +    Buf.newParcel(REQUEST_CDMA_SET_ROAMING_PREFERENCE, options);
  1.1250 +    Buf.writeInt32(1);
  1.1251 +    Buf.writeInt32(roamingMode);
  1.1252 +    Buf.sendParcel();
  1.1253 +  },
  1.1254 +
  1.1255 +  /**
  1.1256 +   * Get the roaming preference mode
  1.1257 +   */
  1.1258 +  queryRoamingPreference: function(options) {
  1.1259 +    this.context.Buf.simpleRequest(REQUEST_CDMA_QUERY_ROAMING_PREFERENCE, options);
  1.1260 +  },
  1.1261 +
  1.1262 +  /**
  1.1263 +   * Set the voice privacy mode
  1.1264 +   */
  1.1265 +  setVoicePrivacyMode: function(options) {
  1.1266 +    let Buf = this.context.Buf;
  1.1267 +    Buf.newParcel(REQUEST_CDMA_SET_PREFERRED_VOICE_PRIVACY_MODE, options);
  1.1268 +    Buf.writeInt32(1);
  1.1269 +    Buf.writeInt32(options.enabled ? 1 : 0);
  1.1270 +    Buf.sendParcel();
  1.1271 +  },
  1.1272 +
  1.1273 +  /**
  1.1274 +   * Get the voice privacy mode
  1.1275 +   */
  1.1276 +  queryVoicePrivacyMode: function(options) {
  1.1277 +    this.context.Buf.simpleRequest(REQUEST_CDMA_QUERY_PREFERRED_VOICE_PRIVACY_MODE, options);
  1.1278 +  },
  1.1279 +
  1.1280 +  /**
  1.1281 +   * Open Logical UICC channel (aid) for Secure Element access
  1.1282 +   */
  1.1283 +  iccOpenChannel: function(options) {
  1.1284 +    if (DEBUG) {
  1.1285 +      this.context.debug("iccOpenChannel: " + JSON.stringify(options));
  1.1286 +    }
  1.1287 +
  1.1288 +    let Buf = this.context.Buf;
  1.1289 +    Buf.newParcel(REQUEST_SIM_OPEN_CHANNEL, options);
  1.1290 +    Buf.writeString(options.aid);
  1.1291 +    Buf.sendParcel();
  1.1292 +  },
  1.1293 +
  1.1294 +/**
  1.1295 +   * Exchange APDU data on an open Logical UICC channel
  1.1296 +   */
  1.1297 +  iccExchangeAPDU: function(options) {
  1.1298 +    if (DEBUG) this.context.debug("iccExchangeAPDU: " + JSON.stringify(options));
  1.1299 +
  1.1300 +    let cla = options.apdu.cla;
  1.1301 +    let command = options.apdu.command;
  1.1302 +    let channel = options.channel;
  1.1303 +    let path = options.apdu.path || "";
  1.1304 +    let data = options.apdu.data || "";
  1.1305 +    let data2 = options.apdu.data2 || "";
  1.1306 +
  1.1307 +    let p1 = options.apdu.p1;
  1.1308 +    let p2 = options.apdu.p2;
  1.1309 +    let p3 = options.apdu.p3; // Extra
  1.1310 +
  1.1311 +    let Buf = this.context.Buf;
  1.1312 +    Buf.newParcel(REQUEST_SIM_ACCESS_CHANNEL, options);
  1.1313 +    Buf.writeInt32(cla);
  1.1314 +    Buf.writeInt32(command);
  1.1315 +    Buf.writeInt32(channel);
  1.1316 +    Buf.writeString(path); // path
  1.1317 +    Buf.writeInt32(p1);
  1.1318 +    Buf.writeInt32(p2);
  1.1319 +    Buf.writeInt32(p3);
  1.1320 +    Buf.writeString(data); // generic data field.
  1.1321 +    Buf.writeString(data2);
  1.1322 +
  1.1323 +    Buf.sendParcel();
  1.1324 +  },
  1.1325 +
  1.1326 +  /**
  1.1327 +   * Close Logical UICC channel
  1.1328 +   */
  1.1329 +  iccCloseChannel: function(options) {
  1.1330 +    if (DEBUG) this.context.debug("iccCloseChannel: " + JSON.stringify(options));
  1.1331 +
  1.1332 +    let Buf = this.context.Buf;
  1.1333 +    Buf.newParcel(REQUEST_SIM_CLOSE_CHANNEL, options);
  1.1334 +    Buf.writeInt32(1);
  1.1335 +    Buf.writeInt32(options.channel);
  1.1336 +    Buf.sendParcel();
  1.1337 +  },
  1.1338 +
  1.1339 +  /**
  1.1340 +   * Tell the radio to choose a specific voice/data network
  1.1341 +   */
  1.1342 +  selectNetwork: function(options) {
  1.1343 +    if (DEBUG) {
  1.1344 +      this.context.debug("Setting manual network selection: " +
  1.1345 +                         options.mcc + ", " + options.mnc);
  1.1346 +    }
  1.1347 +
  1.1348 +    let numeric = (options.mcc && options.mnc) ? options.mcc + options.mnc : null;
  1.1349 +    let Buf = this.context.Buf;
  1.1350 +    Buf.newParcel(REQUEST_SET_NETWORK_SELECTION_MANUAL, options);
  1.1351 +    Buf.writeString(numeric);
  1.1352 +    Buf.sendParcel();
  1.1353 +  },
  1.1354 +
  1.1355 +  /**
  1.1356 +   * Get current calls.
  1.1357 +   */
  1.1358 +  getCurrentCalls: function() {
  1.1359 +    this.context.Buf.simpleRequest(REQUEST_GET_CURRENT_CALLS);
  1.1360 +  },
  1.1361 +
  1.1362 +  /**
  1.1363 +   * Get the signal strength.
  1.1364 +   */
  1.1365 +  getSignalStrength: function() {
  1.1366 +    this.context.Buf.simpleRequest(REQUEST_SIGNAL_STRENGTH);
  1.1367 +  },
  1.1368 +
  1.1369 +  getIMEI: function(options) {
  1.1370 +    this.context.Buf.simpleRequest(REQUEST_GET_IMEI, options);
  1.1371 +  },
  1.1372 +
  1.1373 +  getIMEISV: function() {
  1.1374 +    this.context.Buf.simpleRequest(REQUEST_GET_IMEISV);
  1.1375 +  },
  1.1376 +
  1.1377 +  getDeviceIdentity: function() {
  1.1378 +    this.context.Buf.simpleRequest(REQUEST_DEVICE_IDENTITY);
  1.1379 +  },
  1.1380 +
  1.1381 +  getBasebandVersion: function() {
  1.1382 +    this.context.Buf.simpleRequest(REQUEST_BASEBAND_VERSION);
  1.1383 +  },
  1.1384 +
  1.1385 +  sendExitEmergencyCbModeRequest: function(options) {
  1.1386 +    this.context.Buf.simpleRequest(REQUEST_EXIT_EMERGENCY_CALLBACK_MODE, options);
  1.1387 +  },
  1.1388 +
  1.1389 +  getCdmaSubscription: function() {
  1.1390 +    this.context.Buf.simpleRequest(REQUEST_CDMA_SUBSCRIPTION);
  1.1391 +  },
  1.1392 +
  1.1393 +  exitEmergencyCbMode: function(options) {
  1.1394 +    // The function could be called by an API from RadioInterfaceLayer or by
  1.1395 +    // ril_worker itself. From ril_worker, we won't pass the parameter
  1.1396 +    // 'options'. In this case, it is marked as internal.
  1.1397 +    if (!options) {
  1.1398 +      options = {internal: true};
  1.1399 +    }
  1.1400 +    this._cancelEmergencyCbModeTimeout();
  1.1401 +    this.sendExitEmergencyCbModeRequest(options);
  1.1402 +  },
  1.1403 +
  1.1404 +  /**
  1.1405 +   * Cache the request for making an emergency call when radio is off. The
  1.1406 +   * request shall include two types of callback functions. 'callback' is
  1.1407 +   * called when radio is ready, and 'onerror' is called when turning radio
  1.1408 +   * on fails.
  1.1409 +   */
  1.1410 +  cachedDialRequest : null,
  1.1411 +
  1.1412 +  /**
  1.1413 +   * Dial the phone.
  1.1414 +   *
  1.1415 +   * @param number
  1.1416 +   *        String containing the number to dial.
  1.1417 +   * @param clirMode
  1.1418 +   *        Integer for showing/hidding the caller Id to the called party.
  1.1419 +   * @param uusInfo
  1.1420 +   *        Integer doing something XXX TODO
  1.1421 +   */
  1.1422 +  dial: function(options) {
  1.1423 +    let onerror = (function onerror(options, errorMsg) {
  1.1424 +      options.success = false;
  1.1425 +      options.errorMsg = errorMsg;
  1.1426 +      this.sendChromeMessage(options);
  1.1427 +    }).bind(this, options);
  1.1428 +
  1.1429 +    if (this._isEmergencyNumber(options.number)) {
  1.1430 +      this.dialEmergencyNumber(options, onerror);
  1.1431 +    } else {
  1.1432 +      if (!this._isCdma) {
  1.1433 +        // TODO: Both dial() and sendMMI() functions should be unified at some
  1.1434 +        // point in the future. In the mean time we handle temporary CLIR MMI
  1.1435 +        // commands through the dial() function. Please see bug 889737.
  1.1436 +        let mmi = this._parseMMI(options.number);
  1.1437 +        if (mmi && this._isTemporaryModeCLIR(mmi)) {
  1.1438 +          options.number = mmi.dialNumber;
  1.1439 +          // In temporary mode, MMI_PROCEDURE_ACTIVATION means allowing CLI
  1.1440 +          // presentation, i.e. CLIR_SUPPRESSION. See TS 22.030, Annex B.
  1.1441 +          options.clirMode = mmi.procedure == MMI_PROCEDURE_ACTIVATION ?
  1.1442 +                             CLIR_SUPPRESSION : CLIR_INVOCATION;
  1.1443 +        }
  1.1444 +      }
  1.1445 +      this.dialNonEmergencyNumber(options, onerror);
  1.1446 +    }
  1.1447 +  },
  1.1448 +
  1.1449 +  dialNonEmergencyNumber: function(options, onerror) {
  1.1450 +    if (this.radioState == GECKO_RADIOSTATE_OFF) {
  1.1451 +      // Notify error in establishing the call without radio.
  1.1452 +      onerror(GECKO_ERROR_RADIO_NOT_AVAILABLE);
  1.1453 +      return;
  1.1454 +    }
  1.1455 +
  1.1456 +    if (this.voiceRegistrationState.emergencyCallsOnly ||
  1.1457 +        options.isDialEmergency) {
  1.1458 +      onerror(RIL_CALL_FAILCAUSE_TO_GECKO_CALL_ERROR[CALL_FAIL_UNOBTAINABLE_NUMBER]);
  1.1459 +      return;
  1.1460 +    }
  1.1461 +
  1.1462 +    // Exit emergency callback mode when user dial a non-emergency call.
  1.1463 +    if (this._isInEmergencyCbMode) {
  1.1464 +      this.exitEmergencyCbMode();
  1.1465 +    }
  1.1466 +
  1.1467 +    if (this._isCdma && Object.keys(this.currentCalls).length == 1) {
  1.1468 +      // Make a Cdma 3way call.
  1.1469 +      options.featureStr = options.number;
  1.1470 +      this.sendCdmaFlashCommand(options);
  1.1471 +    } else {
  1.1472 +      options.request = REQUEST_DIAL;
  1.1473 +      this.sendDialRequest(options);
  1.1474 +    }
  1.1475 +  },
  1.1476 +
  1.1477 +  dialEmergencyNumber: function(options, onerror) {
  1.1478 +    options.request = RILQUIRKS_REQUEST_USE_DIAL_EMERGENCY_CALL ?
  1.1479 +                      REQUEST_DIAL_EMERGENCY_CALL : REQUEST_DIAL;
  1.1480 +    if (this.radioState == GECKO_RADIOSTATE_OFF) {
  1.1481 +      if (DEBUG) {
  1.1482 +        this.context.debug("Automatically enable radio for an emergency call.");
  1.1483 +      }
  1.1484 +
  1.1485 +      if (!this.cachedDialRequest) {
  1.1486 +        this.cachedDialRequest = {};
  1.1487 +      }
  1.1488 +      this.cachedDialRequest.onerror = onerror;
  1.1489 +      this.cachedDialRequest.callback = this.sendDialRequest.bind(this, options);
  1.1490 +      this.setRadioEnabled({enabled: true});
  1.1491 +      return;
  1.1492 +    }
  1.1493 +
  1.1494 +    if (this._isCdma && Object.keys(this.currentCalls).length == 1) {
  1.1495 +      // Make a Cdma 3way call.
  1.1496 +      options.featureStr = options.number;
  1.1497 +      this.sendCdmaFlashCommand(options);
  1.1498 +    } else {
  1.1499 +      this.sendDialRequest(options);
  1.1500 +    }
  1.1501 +  },
  1.1502 +
  1.1503 +  sendDialRequest: function(options) {
  1.1504 +    // Always succeed.
  1.1505 +    options.success = true;
  1.1506 +    this.sendChromeMessage(options);
  1.1507 +    this._createPendingOutgoingCall(options);
  1.1508 +
  1.1509 +    let Buf = this.context.Buf;
  1.1510 +    Buf.newParcel(options.request, options);
  1.1511 +    Buf.writeString(options.number);
  1.1512 +    Buf.writeInt32(options.clirMode || 0);
  1.1513 +    Buf.writeInt32(options.uusInfo || 0);
  1.1514 +    // TODO Why do we need this extra 0? It was put it in to make this
  1.1515 +    // match the format of the binary message.
  1.1516 +    Buf.writeInt32(0);
  1.1517 +    Buf.sendParcel();
  1.1518 +  },
  1.1519 +
  1.1520 +  sendCdmaFlashCommand: function(options) {
  1.1521 +    let Buf = this.context.Buf;
  1.1522 +    options.isCdma = true;
  1.1523 +    options.request = REQUEST_CDMA_FLASH;
  1.1524 +    Buf.newParcel(options.request, options);
  1.1525 +    Buf.writeString(options.featureStr);
  1.1526 +    Buf.sendParcel();
  1.1527 +  },
  1.1528 +
  1.1529 +  /**
  1.1530 +   * Hang up all calls
  1.1531 +   */
  1.1532 +  hangUpAll: function() {
  1.1533 +    for (let callIndex in this.currentCalls) {
  1.1534 +      this.hangUp({callIndex: callIndex});
  1.1535 +    }
  1.1536 +  },
  1.1537 +
  1.1538 +  /**
  1.1539 +   * Hang up the phone.
  1.1540 +   *
  1.1541 +   * @param callIndex
  1.1542 +   *        Call index (1-based) as reported by REQUEST_GET_CURRENT_CALLS.
  1.1543 +   */
  1.1544 +  hangUp: function(options) {
  1.1545 +    let call = this.currentCalls[options.callIndex];
  1.1546 +    if (!call) {
  1.1547 +      return;
  1.1548 +    }
  1.1549 +
  1.1550 +    let callIndex = call.callIndex;
  1.1551 +    if (callIndex === OUTGOING_PLACEHOLDER_CALL_INDEX) {
  1.1552 +      if (DEBUG) this.context.debug("Hang up pending outgoing call.");
  1.1553 +      this._removeVoiceCall(call, GECKO_CALL_ERROR_NORMAL_CALL_CLEARING);
  1.1554 +      return;
  1.1555 +    }
  1.1556 +
  1.1557 +    call.hangUpLocal = true;
  1.1558 +
  1.1559 +    if (call.state === CALL_STATE_HOLDING) {
  1.1560 +      this.sendHangUpBackgroundRequest(callIndex);
  1.1561 +    } else {
  1.1562 +      this.sendHangUpRequest(callIndex);
  1.1563 +    }
  1.1564 +  },
  1.1565 +
  1.1566 +  sendHangUpRequest: function(callIndex) {
  1.1567 +    let Buf = this.context.Buf;
  1.1568 +    Buf.newParcel(REQUEST_HANGUP);
  1.1569 +    Buf.writeInt32(1);
  1.1570 +    Buf.writeInt32(callIndex);
  1.1571 +    Buf.sendParcel();
  1.1572 +  },
  1.1573 +
  1.1574 +  sendHangUpBackgroundRequest: function(callIndex) {
  1.1575 +    let Buf = this.context.Buf;
  1.1576 +    Buf.simpleRequest(REQUEST_HANGUP_WAITING_OR_BACKGROUND);
  1.1577 +  },
  1.1578 +
  1.1579 +  /**
  1.1580 +   * Mute or unmute the radio.
  1.1581 +   *
  1.1582 +   * @param mute
  1.1583 +   *        Boolean to indicate whether to mute or unmute the radio.
  1.1584 +   */
  1.1585 +  setMute: function(options) {
  1.1586 +    let Buf = this.context.Buf;
  1.1587 +    Buf.newParcel(REQUEST_SET_MUTE);
  1.1588 +    Buf.writeInt32(1);
  1.1589 +    Buf.writeInt32(options.muted ? 1 : 0);
  1.1590 +    Buf.sendParcel();
  1.1591 +  },
  1.1592 +
  1.1593 +  /**
  1.1594 +   * Answer an incoming/waiting call.
  1.1595 +   *
  1.1596 +   * @param callIndex
  1.1597 +   *        Call index of the call to answer.
  1.1598 +   */
  1.1599 +  answerCall: function(options) {
  1.1600 +    // Check for races. Since we dispatched the incoming/waiting call
  1.1601 +    // notification the incoming/waiting call may have changed. The main
  1.1602 +    // thread thinks that it is answering the call with the given index,
  1.1603 +    // so only answer if that is still incoming/waiting.
  1.1604 +    let call = this.currentCalls[options.callIndex];
  1.1605 +    if (!call) {
  1.1606 +      return;
  1.1607 +    }
  1.1608 +
  1.1609 +    let Buf = this.context.Buf;
  1.1610 +    switch (call.state) {
  1.1611 +      case CALL_STATE_INCOMING:
  1.1612 +        Buf.simpleRequest(REQUEST_ANSWER);
  1.1613 +        break;
  1.1614 +      case CALL_STATE_WAITING:
  1.1615 +        // Answer the waiting (second) call, and hold the first call.
  1.1616 +        Buf.simpleRequest(REQUEST_SWITCH_WAITING_OR_HOLDING_AND_ACTIVE);
  1.1617 +        break;
  1.1618 +    }
  1.1619 +  },
  1.1620 +
  1.1621 +  /**
  1.1622 +   * Reject an incoming/waiting call.
  1.1623 +   *
  1.1624 +   * @param callIndex
  1.1625 +   *        Call index of the call to reject.
  1.1626 +   */
  1.1627 +  rejectCall: function(options) {
  1.1628 +    // Check for races. Since we dispatched the incoming/waiting call
  1.1629 +    // notification the incoming/waiting call may have changed. The main
  1.1630 +    // thread thinks that it is rejecting the call with the given index,
  1.1631 +    // so only reject if that is still incoming/waiting.
  1.1632 +    let call = this.currentCalls[options.callIndex];
  1.1633 +    if (!call) {
  1.1634 +      return;
  1.1635 +    }
  1.1636 +
  1.1637 +    call.hangUpLocal = true;
  1.1638 +
  1.1639 +    let Buf = this.context.Buf;
  1.1640 +    if (this._isCdma) {
  1.1641 +      // AT+CHLD=0 means "release held or UDUB."
  1.1642 +      Buf.simpleRequest(REQUEST_HANGUP_WAITING_OR_BACKGROUND);
  1.1643 +      return;
  1.1644 +    }
  1.1645 +
  1.1646 +    switch (call.state) {
  1.1647 +      case CALL_STATE_INCOMING:
  1.1648 +        Buf.simpleRequest(REQUEST_UDUB);
  1.1649 +        break;
  1.1650 +      case CALL_STATE_WAITING:
  1.1651 +        // Reject the waiting (second) call, and remain the first call.
  1.1652 +        Buf.simpleRequest(REQUEST_HANGUP_WAITING_OR_BACKGROUND);
  1.1653 +        break;
  1.1654 +    }
  1.1655 +  },
  1.1656 +
  1.1657 +  holdCall: function(options) {
  1.1658 +    let call = this.currentCalls[options.callIndex];
  1.1659 +    if (!call) {
  1.1660 +      options.errorMsg = GECKO_ERROR_GENERIC_FAILURE;
  1.1661 +      options.success = false;
  1.1662 +      this.sendChromeMessage(options);
  1.1663 +      return;
  1.1664 +    }
  1.1665 +
  1.1666 +    let Buf = this.context.Buf;
  1.1667 +    if (this._isCdma) {
  1.1668 +      options.featureStr = "";
  1.1669 +      this.sendCdmaFlashCommand(options);
  1.1670 +    } else if (call.state == CALL_STATE_ACTIVE) {
  1.1671 +      Buf.simpleRequest(REQUEST_SWITCH_WAITING_OR_HOLDING_AND_ACTIVE, options);
  1.1672 +    }
  1.1673 + },
  1.1674 +
  1.1675 +  resumeCall: function(options) {
  1.1676 +    let call = this.currentCalls[options.callIndex];
  1.1677 +    if (!call) {
  1.1678 +      options.errorMsg = GECKO_ERROR_GENERIC_FAILURE;
  1.1679 +      options.success = false;
  1.1680 +      this.sendChromeMessage(options);
  1.1681 +      return;
  1.1682 +    }
  1.1683 +
  1.1684 +    let Buf = this.context.Buf;
  1.1685 +    if (this._isCdma) {
  1.1686 +      options.featureStr = "";
  1.1687 +      this.sendCdmaFlashCommand(options);
  1.1688 +    } else if (call.state == CALL_STATE_HOLDING) {
  1.1689 +      Buf.simpleRequest(REQUEST_SWITCH_WAITING_OR_HOLDING_AND_ACTIVE, options);
  1.1690 +    }
  1.1691 +  },
  1.1692 +
  1.1693 +  // Flag indicating whether user has requested making a conference call.
  1.1694 +  _hasConferenceRequest: false,
  1.1695 +
  1.1696 +  conferenceCall: function(options) {
  1.1697 +    let Buf = this.context.Buf;
  1.1698 +    if (this._isCdma) {
  1.1699 +      options.featureStr = "";
  1.1700 +      this.sendCdmaFlashCommand(options);
  1.1701 +    } else {
  1.1702 +      this._hasConferenceRequest = true;
  1.1703 +      Buf.simpleRequest(REQUEST_CONFERENCE, options);
  1.1704 +    }
  1.1705 +  },
  1.1706 +
  1.1707 +  separateCall: function(options) {
  1.1708 +    let call = this.currentCalls[options.callIndex];
  1.1709 +    if (!call) {
  1.1710 +      options.errorName = "removeError";
  1.1711 +      options.errorMsg = GECKO_ERROR_GENERIC_FAILURE;
  1.1712 +      options.success = false;
  1.1713 +      this.sendChromeMessage(options);
  1.1714 +      return;
  1.1715 +    }
  1.1716 +
  1.1717 +    let Buf = this.context.Buf;
  1.1718 +    if (this._isCdma) {
  1.1719 +      options.featureStr = "";
  1.1720 +      this.sendCdmaFlashCommand(options);
  1.1721 +    } else {
  1.1722 +      Buf.newParcel(REQUEST_SEPARATE_CONNECTION, options);
  1.1723 +      Buf.writeInt32(1);
  1.1724 +      Buf.writeInt32(options.callIndex);
  1.1725 +      Buf.sendParcel();
  1.1726 +    }
  1.1727 + },
  1.1728 +
  1.1729 +  holdConference: function() {
  1.1730 +    if (this._isCdma) {
  1.1731 +      return;
  1.1732 +    }
  1.1733 +
  1.1734 +    this.context.Buf.simpleRequest(REQUEST_SWITCH_WAITING_OR_HOLDING_AND_ACTIVE);
  1.1735 +  },
  1.1736 +
  1.1737 +  resumeConference: function() {
  1.1738 +    if (this._isCdma) {
  1.1739 +      return;
  1.1740 +    }
  1.1741 +
  1.1742 +    this.context.Buf.simpleRequest(REQUEST_SWITCH_WAITING_OR_HOLDING_AND_ACTIVE);
  1.1743 +  },
  1.1744 +
  1.1745 +  /**
  1.1746 +   * Send an SMS.
  1.1747 +   *
  1.1748 +   * The `options` parameter object should contain the following attributes:
  1.1749 +   *
  1.1750 +   * @param number
  1.1751 +   *        String containing the recipient number.
  1.1752 +   * @param body
  1.1753 +   *        String containing the message text.
  1.1754 +   * @param envelopeId
  1.1755 +   *        Numeric value identifying the sms request.
  1.1756 +   */
  1.1757 +  sendSMS: function(options) {
  1.1758 +    options.langIndex = options.langIndex || PDU_NL_IDENTIFIER_DEFAULT;
  1.1759 +    options.langShiftIndex = options.langShiftIndex || PDU_NL_IDENTIFIER_DEFAULT;
  1.1760 +
  1.1761 +    if (!options.retryCount) {
  1.1762 +      options.retryCount = 0;
  1.1763 +    }
  1.1764 +
  1.1765 +    if (!options.segmentSeq) {
  1.1766 +      // Fist segment to send
  1.1767 +      options.segmentSeq = 1;
  1.1768 +      options.body = options.segments[0].body;
  1.1769 +      options.encodedBodyLength = options.segments[0].encodedBodyLength;
  1.1770 +    }
  1.1771 +
  1.1772 +    let Buf = this.context.Buf;
  1.1773 +    if (this._isCdma) {
  1.1774 +      Buf.newParcel(REQUEST_CDMA_SEND_SMS, options);
  1.1775 +      this.context.CdmaPDUHelper.writeMessage(options);
  1.1776 +    } else {
  1.1777 +      Buf.newParcel(REQUEST_SEND_SMS, options);
  1.1778 +      Buf.writeInt32(2);
  1.1779 +      Buf.writeString(options.SMSC);
  1.1780 +      this.context.GsmPDUHelper.writeMessage(options);
  1.1781 +    }
  1.1782 +    Buf.sendParcel();
  1.1783 +  },
  1.1784 +
  1.1785 +  /**
  1.1786 +   * Acknowledge the receipt and handling of an SMS.
  1.1787 +   *
  1.1788 +   * @param success
  1.1789 +   *        Boolean indicating whether the message was successfuly handled.
  1.1790 +   * @param cause
  1.1791 +   *        SMS_* constant indicating the reason for unsuccessful handling.
  1.1792 +   */
  1.1793 +  acknowledgeGsmSms: function(success, cause) {
  1.1794 +    let Buf = this.context.Buf;
  1.1795 +    Buf.newParcel(REQUEST_SMS_ACKNOWLEDGE);
  1.1796 +    Buf.writeInt32(2);
  1.1797 +    Buf.writeInt32(success ? 1 : 0);
  1.1798 +    Buf.writeInt32(cause);
  1.1799 +    Buf.sendParcel();
  1.1800 +  },
  1.1801 +
  1.1802 +  /**
  1.1803 +   * Acknowledge the receipt and handling of an SMS.
  1.1804 +   *
  1.1805 +   * @param success
  1.1806 +   *        Boolean indicating whether the message was successfuly handled.
  1.1807 +   */
  1.1808 +  ackSMS: function(options) {
  1.1809 +    if (options.result == PDU_FCS_RESERVED) {
  1.1810 +      return;
  1.1811 +    }
  1.1812 +    if (this._isCdma) {
  1.1813 +      this.acknowledgeCdmaSms(options.result == PDU_FCS_OK, options.result);
  1.1814 +    } else {
  1.1815 +      this.acknowledgeGsmSms(options.result == PDU_FCS_OK, options.result);
  1.1816 +    }
  1.1817 +  },
  1.1818 +
  1.1819 +  /**
  1.1820 +   * Acknowledge the receipt and handling of a CDMA SMS.
  1.1821 +   *
  1.1822 +   * @param success
  1.1823 +   *        Boolean indicating whether the message was successfuly handled.
  1.1824 +   * @param cause
  1.1825 +   *        SMS_* constant indicating the reason for unsuccessful handling.
  1.1826 +   */
  1.1827 +  acknowledgeCdmaSms: function(success, cause) {
  1.1828 +    let Buf = this.context.Buf;
  1.1829 +    Buf.newParcel(REQUEST_CDMA_SMS_ACKNOWLEDGE);
  1.1830 +    Buf.writeInt32(success ? 0 : 1);
  1.1831 +    Buf.writeInt32(cause);
  1.1832 +    Buf.sendParcel();
  1.1833 +  },
  1.1834 +
  1.1835 +  /**
  1.1836 +   * Update received MWI into EF_MWIS.
  1.1837 +   */
  1.1838 +  updateMwis: function(options) {
  1.1839 +    if (this.context.ICCUtilsHelper.isICCServiceAvailable("MWIS")) {
  1.1840 +      this.context.SimRecordHelper.updateMWIS(options.mwi);
  1.1841 +    }
  1.1842 +  },
  1.1843 +
  1.1844 +  setCellBroadcastDisabled: function(options) {
  1.1845 +    this.cellBroadcastDisabled = options.disabled;
  1.1846 +
  1.1847 +    // If |this.mergedCellBroadcastConfig| is null, either we haven't finished
  1.1848 +    // reading required SIM files, or no any channel is ever configured.  In
  1.1849 +    // the former case, we'll call |this.updateCellBroadcastConfig()| later
  1.1850 +    // with correct configs; in the latter case, we don't bother resetting CB
  1.1851 +    // to disabled again.
  1.1852 +    if (this.mergedCellBroadcastConfig) {
  1.1853 +      this.updateCellBroadcastConfig();
  1.1854 +    }
  1.1855 +  },
  1.1856 +
  1.1857 +  setCellBroadcastSearchList: function(options) {
  1.1858 +    let getSearchListStr = function(aSearchList) {
  1.1859 +      if (typeof aSearchList === "string" || aSearchList instanceof String) {
  1.1860 +        return aSearchList;
  1.1861 +      }
  1.1862 +
  1.1863 +      // TODO: Set search list for CDMA/GSM individually. Bug 990926
  1.1864 +      let prop = this._isCdma ? "cdma" : "gsm";
  1.1865 +
  1.1866 +      return aSearchList && aSearchList[prop];
  1.1867 +    }.bind(this);
  1.1868 +
  1.1869 +    try {
  1.1870 +      let str = getSearchListStr(options.searchList);
  1.1871 +      this.cellBroadcastConfigs.MMI = this._convertCellBroadcastSearchList(str);
  1.1872 +      options.success = true;
  1.1873 +    } catch (e) {
  1.1874 +      if (DEBUG) {
  1.1875 +        this.context.debug("Invalid Cell Broadcast search list: " + e);
  1.1876 +      }
  1.1877 +      options.success = false;
  1.1878 +    }
  1.1879 +
  1.1880 +    this.sendChromeMessage(options);
  1.1881 +    if (!options.success) {
  1.1882 +      return;
  1.1883 +    }
  1.1884 +
  1.1885 +    this._mergeAllCellBroadcastConfigs();
  1.1886 +  },
  1.1887 +
  1.1888 +  updateCellBroadcastConfig: function() {
  1.1889 +    let activate = !this.cellBroadcastDisabled &&
  1.1890 +                   (this.mergedCellBroadcastConfig != null) &&
  1.1891 +                   (this.mergedCellBroadcastConfig.length > 0);
  1.1892 +    if (activate) {
  1.1893 +      this.setSmsBroadcastConfig(this.mergedCellBroadcastConfig);
  1.1894 +    } else {
  1.1895 +      // It's unnecessary to set config first if we're deactivating.
  1.1896 +      this.setSmsBroadcastActivation(false);
  1.1897 +    }
  1.1898 +  },
  1.1899 +
  1.1900 +  setGsmSmsBroadcastConfig: function(config) {
  1.1901 +    let Buf = this.context.Buf;
  1.1902 +    Buf.newParcel(REQUEST_GSM_SET_BROADCAST_SMS_CONFIG);
  1.1903 +
  1.1904 +    let numConfigs = config ? config.length / 2 : 0;
  1.1905 +    Buf.writeInt32(numConfigs);
  1.1906 +    for (let i = 0; i < config.length;) {
  1.1907 +      Buf.writeInt32(config[i++]);
  1.1908 +      Buf.writeInt32(config[i++]);
  1.1909 +      Buf.writeInt32(0x00);
  1.1910 +      Buf.writeInt32(0xFF);
  1.1911 +      Buf.writeInt32(1);
  1.1912 +    }
  1.1913 +
  1.1914 +    Buf.sendParcel();
  1.1915 +  },
  1.1916 +
  1.1917 +  /**
  1.1918 +   * Send CDMA SMS broadcast config.
  1.1919 +   *
  1.1920 +   * @see 3GPP2 C.R1001 Sec. 9.2 and 9.3
  1.1921 +   */
  1.1922 +  setCdmaSmsBroadcastConfig: function(config) {
  1.1923 +    let Buf = this.context.Buf;
  1.1924 +    // |config| is an array of half-closed range: [[from, to), [from, to), ...].
  1.1925 +    // It will be further decomposed, ex: [1, 4) => 1, 2, 3.
  1.1926 +    Buf.newParcel(REQUEST_CDMA_SET_BROADCAST_SMS_CONFIG);
  1.1927 +
  1.1928 +    let numConfigs = 0;
  1.1929 +    for (let i = 0; i < config.length; i += 2) {
  1.1930 +      numConfigs += (config[i+1] - config[i]);
  1.1931 +    }
  1.1932 +
  1.1933 +    Buf.writeInt32(numConfigs);
  1.1934 +    for (let i = 0; i < config.length;) {
  1.1935 +      let begin = config[i++];
  1.1936 +      let end = config[i++];
  1.1937 +
  1.1938 +      for (let j = begin; j < end; ++j) {
  1.1939 +        Buf.writeInt32(j);
  1.1940 +        Buf.writeInt32(0);  // Language Indicator: Unknown or unspecified.
  1.1941 +        Buf.writeInt32(1);
  1.1942 +      }
  1.1943 +    }
  1.1944 +
  1.1945 +    Buf.sendParcel();
  1.1946 +  },
  1.1947 +
  1.1948 +  setSmsBroadcastConfig: function(config) {
  1.1949 +    if (this._isCdma) {
  1.1950 +      this.setCdmaSmsBroadcastConfig(config);
  1.1951 +    } else {
  1.1952 +      this.setGsmSmsBroadcastConfig(config);
  1.1953 +    }
  1.1954 +  },
  1.1955 +
  1.1956 +  setSmsBroadcastActivation: function(activate) {
  1.1957 +    let parcelType = this._isCdma ? REQUEST_CDMA_SMS_BROADCAST_ACTIVATION :
  1.1958 +                                    REQUEST_GSM_SMS_BROADCAST_ACTIVATION;
  1.1959 +    let Buf = this.context.Buf;
  1.1960 +    Buf.newParcel(parcelType);
  1.1961 +    Buf.writeInt32(1);
  1.1962 +    // See hardware/ril/include/telephony/ril.h, 0 - Activate, 1 - Turn off.
  1.1963 +    Buf.writeInt32(activate ? 0 : 1);
  1.1964 +    Buf.sendParcel();
  1.1965 +  },
  1.1966 +
  1.1967 +  /**
  1.1968 +   * Start a DTMF Tone.
  1.1969 +   *
  1.1970 +   * @param dtmfChar
  1.1971 +   *        DTMF signal to send, 0-9, *, +
  1.1972 +   */
  1.1973 +  startTone: function(options) {
  1.1974 +    let Buf = this.context.Buf;
  1.1975 +    Buf.newParcel(REQUEST_DTMF_START);
  1.1976 +    Buf.writeString(options.dtmfChar);
  1.1977 +    Buf.sendParcel();
  1.1978 +  },
  1.1979 +
  1.1980 +  stopTone: function() {
  1.1981 +    this.context.Buf.simpleRequest(REQUEST_DTMF_STOP);
  1.1982 +  },
  1.1983 +
  1.1984 +  /**
  1.1985 +   * Send a DTMF tone.
  1.1986 +   *
  1.1987 +   * @param dtmfChar
  1.1988 +   *        DTMF signal to send, 0-9, *, +
  1.1989 +   */
  1.1990 +  sendTone: function(options) {
  1.1991 +    let Buf = this.context.Buf;
  1.1992 +    Buf.newParcel(REQUEST_DTMF);
  1.1993 +    Buf.writeString(options.dtmfChar);
  1.1994 +    Buf.sendParcel();
  1.1995 +  },
  1.1996 +
  1.1997 +  /**
  1.1998 +   * Get the Short Message Service Center address.
  1.1999 +   */
  1.2000 +  getSmscAddress: function(options) {
  1.2001 +    if (!this.SMSC) {
  1.2002 +      this.context.Buf.simpleRequest(REQUEST_GET_SMSC_ADDRESS, options);
  1.2003 +      return;
  1.2004 +    }
  1.2005 +
  1.2006 +    if (!options || options.rilMessageType !== "getSmscAddress") {
  1.2007 +      return;
  1.2008 +    }
  1.2009 +
  1.2010 +    options.smscAddress = this.SMSC;
  1.2011 +    options.errorMsg = RIL_ERROR_TO_GECKO_ERROR[options.rilRequestError];
  1.2012 +    this.sendChromeMessage(options);
  1.2013 +  },
  1.2014 +
  1.2015 +  /**
  1.2016 +   * Set the Short Message Service Center address.
  1.2017 +   *
  1.2018 +   * @param smscAddress
  1.2019 +   *        Short Message Service Center address in PDU format.
  1.2020 +   */
  1.2021 +  setSmscAddress: function(options) {
  1.2022 +    let Buf = this.context.Buf;
  1.2023 +    Buf.newParcel(REQUEST_SET_SMSC_ADDRESS, options);
  1.2024 +    Buf.writeString(options.smscAddress);
  1.2025 +    Buf.sendParcel();
  1.2026 +  },
  1.2027 +
  1.2028 +  /**
  1.2029 +   * Setup a data call.
  1.2030 +   *
  1.2031 +   * @param radioTech
  1.2032 +   *        Integer to indicate radio technology.
  1.2033 +   *        DATACALL_RADIOTECHNOLOGY_CDMA => CDMA.
  1.2034 +   *        DATACALL_RADIOTECHNOLOGY_GSM  => GSM.
  1.2035 +   * @param apn
  1.2036 +   *        String containing the name of the APN to connect to.
  1.2037 +   * @param user
  1.2038 +   *        String containing the username for the APN.
  1.2039 +   * @param passwd
  1.2040 +   *        String containing the password for the APN.
  1.2041 +   * @param chappap
  1.2042 +   *        Integer containing CHAP/PAP auth type.
  1.2043 +   *        DATACALL_AUTH_NONE        => PAP and CHAP is never performed.
  1.2044 +   *        DATACALL_AUTH_PAP         => PAP may be performed.
  1.2045 +   *        DATACALL_AUTH_CHAP        => CHAP may be performed.
  1.2046 +   *        DATACALL_AUTH_PAP_OR_CHAP => PAP / CHAP may be performed.
  1.2047 +   * @param pdptype
  1.2048 +   *        String containing PDP type to request. ("IP", "IPV6", ...)
  1.2049 +   */
  1.2050 +  setupDataCall: function(options) {
  1.2051 +    // From ./hardware/ril/include/telephony/ril.h:
  1.2052 +    // ((const char **)data)[0] Radio technology to use: 0-CDMA, 1-GSM/UMTS, 2...
  1.2053 +    // for values above 2 this is RIL_RadioTechnology + 2.
  1.2054 +    //
  1.2055 +    // From frameworks/base/telephony/java/com/android/internal/telephony/DataConnection.java:
  1.2056 +    // if the mRilVersion < 6, radio technology must be GSM/UMTS or CDMA.
  1.2057 +    // Otherwise, it must be + 2
  1.2058 +    //
  1.2059 +    // See also bug 901232 and 867873
  1.2060 +    let radioTech;
  1.2061 +    if (this.v5Legacy) {
  1.2062 +      radioTech = this._isCdma ? DATACALL_RADIOTECHNOLOGY_CDMA
  1.2063 +                               : DATACALL_RADIOTECHNOLOGY_GSM;
  1.2064 +    } else {
  1.2065 +      radioTech = options.radioTech + 2;
  1.2066 +    }
  1.2067 +    let Buf = this.context.Buf;
  1.2068 +    let token = Buf.newParcel(REQUEST_SETUP_DATA_CALL, options);
  1.2069 +    Buf.writeInt32(7);
  1.2070 +    Buf.writeString(radioTech.toString());
  1.2071 +    Buf.writeString(DATACALL_PROFILE_DEFAULT.toString());
  1.2072 +    Buf.writeString(options.apn);
  1.2073 +    Buf.writeString(options.user);
  1.2074 +    Buf.writeString(options.passwd);
  1.2075 +    Buf.writeString(options.chappap.toString());
  1.2076 +    Buf.writeString(options.pdptype);
  1.2077 +    Buf.sendParcel();
  1.2078 +    return token;
  1.2079 +  },
  1.2080 +
  1.2081 +  /**
  1.2082 +   * Deactivate a data call.
  1.2083 +   *
  1.2084 +   * @param cid
  1.2085 +   *        String containing CID.
  1.2086 +   * @param reason
  1.2087 +   *        One of DATACALL_DEACTIVATE_* constants.
  1.2088 +   */
  1.2089 +  deactivateDataCall: function(options) {
  1.2090 +    let datacall = this.currentDataCalls[options.cid];
  1.2091 +    if (!datacall) {
  1.2092 +      return;
  1.2093 +    }
  1.2094 +
  1.2095 +    let Buf = this.context.Buf;
  1.2096 +    Buf.newParcel(REQUEST_DEACTIVATE_DATA_CALL, options);
  1.2097 +    Buf.writeInt32(2);
  1.2098 +    Buf.writeString(options.cid);
  1.2099 +    Buf.writeString(options.reason || DATACALL_DEACTIVATE_NO_REASON);
  1.2100 +    Buf.sendParcel();
  1.2101 +
  1.2102 +    datacall.state = GECKO_NETWORK_STATE_DISCONNECTING;
  1.2103 +    this.sendChromeMessage(datacall);
  1.2104 +  },
  1.2105 +
  1.2106 +  /**
  1.2107 +   * Get a list of data calls.
  1.2108 +   */
  1.2109 +  getDataCallList: function() {
  1.2110 +    this.context.Buf.simpleRequest(REQUEST_DATA_CALL_LIST);
  1.2111 +  },
  1.2112 +
  1.2113 +  _attachDataRegistration: false,
  1.2114 +  /**
  1.2115 +   * Manually attach/detach data registration.
  1.2116 +   *
  1.2117 +   * @param attach
  1.2118 +   *        Boolean value indicating attach or detach.
  1.2119 +   */
  1.2120 +  setDataRegistration: function(options) {
  1.2121 +    let request = options.attach ? RIL_REQUEST_GPRS_ATTACH :
  1.2122 +                                   RIL_REQUEST_GPRS_DETACH;
  1.2123 +    this._attachDataRegistration = options.attach;
  1.2124 +    this.context.Buf.simpleRequest(request);
  1.2125 +  },
  1.2126 +
  1.2127 +  /**
  1.2128 +   * Get failure casue code for the most recently failed PDP context.
  1.2129 +   */
  1.2130 +  getFailCauseCode: function(callback) {
  1.2131 +    this.context.Buf.simpleRequest(REQUEST_LAST_CALL_FAIL_CAUSE,
  1.2132 +                                   {callback: callback});
  1.2133 +  },
  1.2134 +
  1.2135 +  /**
  1.2136 +   * Helper to parse MMI/USSD string. TS.22.030 Figure 3.5.3.2.
  1.2137 +   */
  1.2138 +  _parseMMI: function(mmiString) {
  1.2139 +    if (!mmiString || !mmiString.length) {
  1.2140 +      return null;
  1.2141 +    }
  1.2142 +
  1.2143 +    let matches = this._matchMMIRegexp(mmiString);
  1.2144 +    if (matches) {
  1.2145 +      // After successfully executing the regular expresion over the MMI string,
  1.2146 +      // the following match groups should contain:
  1.2147 +      // 1 = full MMI string that might be used as a USSD request.
  1.2148 +      // 2 = MMI procedure.
  1.2149 +      // 3 = Service code.
  1.2150 +      // 5 = SIA.
  1.2151 +      // 7 = SIB.
  1.2152 +      // 9 = SIC.
  1.2153 +      // 11 = Password registration.
  1.2154 +      // 12 = Dialing number.
  1.2155 +      return {
  1.2156 +        fullMMI: matches[MMI_MATCH_GROUP_FULL_MMI],
  1.2157 +        procedure: matches[MMI_MATCH_GROUP_MMI_PROCEDURE],
  1.2158 +        serviceCode: matches[MMI_MATCH_GROUP_SERVICE_CODE],
  1.2159 +        sia: matches[MMI_MATCH_GROUP_SIA],
  1.2160 +        sib: matches[MMI_MATCH_GROUP_SIB],
  1.2161 +        sic: matches[MMI_MATCH_GROUP_SIC],
  1.2162 +        pwd: matches[MMI_MATCH_GROUP_PWD_CONFIRM],
  1.2163 +        dialNumber: matches[MMI_MATCH_GROUP_DIALING_NUMBER]
  1.2164 +      };
  1.2165 +    }
  1.2166 +
  1.2167 +    if (this._isPoundString(mmiString) ||
  1.2168 +        this._isMMIShortString(mmiString)) {
  1.2169 +      return {
  1.2170 +        fullMMI: mmiString
  1.2171 +      };
  1.2172 +    }
  1.2173 +
  1.2174 +    return null;
  1.2175 +  },
  1.2176 +
  1.2177 +  /**
  1.2178 +   * Helper to parse MMI string via regular expression. TS.22.030 Figure
  1.2179 +   * 3.5.3.2.
  1.2180 +   */
  1.2181 +  _matchMMIRegexp: function(mmiString) {
  1.2182 +    // Regexp to parse and process the MMI code.
  1.2183 +    if (this._mmiRegExp == null) {
  1.2184 +      // The first group of the regexp takes the whole MMI string.
  1.2185 +      // The second group takes the MMI procedure that can be:
  1.2186 +      //    - Activation (*SC*SI#).
  1.2187 +      //    - Deactivation (#SC*SI#).
  1.2188 +      //    - Interrogation (*#SC*SI#).
  1.2189 +      //    - Registration (**SC*SI#).
  1.2190 +      //    - Erasure (##SC*SI#).
  1.2191 +      //  where SC = Service Code (2 or 3 digits) and SI = Supplementary Info
  1.2192 +      //  (variable length).
  1.2193 +      let pattern = "((\\*[*#]?|##?)";
  1.2194 +
  1.2195 +      // Third group of the regexp looks for the MMI Service code, which is a
  1.2196 +      // 2 or 3 digits that uniquely specifies the Supplementary Service
  1.2197 +      // associated with the MMI code.
  1.2198 +      pattern += "(\\d{2,3})";
  1.2199 +
  1.2200 +      // Groups from 4 to 9 looks for the MMI Supplementary Information SIA,
  1.2201 +      // SIB and SIC. SIA may comprise e.g. a PIN code or Directory Number,
  1.2202 +      // SIB may be used to specify the tele or bearer service and SIC to
  1.2203 +      // specify the value of the "No Reply Condition Timer". Where a particular
  1.2204 +      // service request does not require any SI, "*SI" is not entered. The use
  1.2205 +      // of SIA, SIB and SIC is optional and shall be entered in any of the
  1.2206 +      // following formats:
  1.2207 +      //    - *SIA*SIB*SIC#
  1.2208 +      //    - *SIA*SIB#
  1.2209 +      //    - *SIA**SIC#
  1.2210 +      //    - *SIA#
  1.2211 +      //    - **SIB*SIC#
  1.2212 +      //    - ***SISC#
  1.2213 +      pattern += "(\\*([^*#]*)(\\*([^*#]*)(\\*([^*#]*)";
  1.2214 +
  1.2215 +      // The eleventh group takes the password for the case of a password
  1.2216 +      // registration procedure.
  1.2217 +      pattern += "(\\*([^*#]*))?)?)?)?#)";
  1.2218 +
  1.2219 +      // The last group takes the dial string after the #.
  1.2220 +      pattern += "([^#]*)";
  1.2221 +
  1.2222 +      this._mmiRegExp = new RegExp(pattern);
  1.2223 +    }
  1.2224 +
  1.2225 +    // Regex only applys for those well-defined MMI strings (refer to TS.22.030
  1.2226 +    // Annex B), otherwise, null should be the expected return value.
  1.2227 +    return this._mmiRegExp.exec(mmiString);
  1.2228 +  },
  1.2229 +
  1.2230 +  /**
  1.2231 +   * Helper to parse # string. TS.22.030 Figure 3.5.3.2.
  1.2232 +   */
  1.2233 +  _isPoundString: function(mmiString) {
  1.2234 +    return (mmiString.charAt(mmiString.length - 1) === MMI_END_OF_USSD);
  1.2235 +  },
  1.2236 +
  1.2237 +  /**
  1.2238 +   * Helper to parse short string. TS.22.030 Figure 3.5.3.2.
  1.2239 +   */
  1.2240 +  _isMMIShortString: function(mmiString) {
  1.2241 +    if (mmiString.length > 2) {
  1.2242 +      return false;
  1.2243 +    }
  1.2244 +
  1.2245 +    if (this._isEmergencyNumber(mmiString)) {
  1.2246 +      return false;
  1.2247 +    }
  1.2248 +
  1.2249 +    // In a call case.
  1.2250 +    if (Object.getOwnPropertyNames(this.currentCalls).length > 0) {
  1.2251 +      return true;
  1.2252 +    }
  1.2253 +
  1.2254 +    if ((mmiString.length != 2) || (mmiString.charAt(0) !== '1')) {
  1.2255 +      return true;
  1.2256 +    }
  1.2257 +
  1.2258 +    return false;
  1.2259 +  },
  1.2260 +
  1.2261 +  sendMMI: function(options) {
  1.2262 +    if (DEBUG) {
  1.2263 +      this.context.debug("SendMMI " + JSON.stringify(options));
  1.2264 +    }
  1.2265 +    let mmiString = options.mmi;
  1.2266 +    let mmi = this._parseMMI(mmiString);
  1.2267 +
  1.2268 +    let _sendMMIError = (function(errorMsg, mmiServiceCode) {
  1.2269 +      options.success = false;
  1.2270 +      options.errorMsg = errorMsg;
  1.2271 +      if (mmiServiceCode) {
  1.2272 +        options.mmiServiceCode = mmiServiceCode;
  1.2273 +      }
  1.2274 +      this.sendChromeMessage(options);
  1.2275 +    }).bind(this);
  1.2276 +
  1.2277 +    function _isValidPINPUKRequest(mmiServiceCode) {
  1.2278 +      // The only allowed MMI procedure for ICC PIN, PIN2, PUK and PUK2 handling
  1.2279 +      // is "Registration" (**).
  1.2280 +      if (!mmi.procedure || mmi.procedure != MMI_PROCEDURE_REGISTRATION ) {
  1.2281 +        _sendMMIError(MMI_ERROR_KS_INVALID_ACTION, mmiServiceCode);
  1.2282 +        return false;
  1.2283 +      }
  1.2284 +
  1.2285 +      if (!mmi.sia || !mmi.sia.length || !mmi.sib || !mmi.sib.length ||
  1.2286 +          !mmi.sic || !mmi.sic.length) {
  1.2287 +        _sendMMIError(MMI_ERROR_KS_ERROR, mmiServiceCode);
  1.2288 +        return false;
  1.2289 +      }
  1.2290 +
  1.2291 +      if (mmi.sib != mmi.sic) {
  1.2292 +        _sendMMIError(MMI_ERROR_KS_MISMATCH_PIN, mmiServiceCode);
  1.2293 +        return false;
  1.2294 +      }
  1.2295 +
  1.2296 +      if (mmi.sia.length < 4 || mmi.sia.length > 8 ||
  1.2297 +          mmi.sib.length < 4 || mmi.sib.length > 8 ||
  1.2298 +          mmi.sic.length < 4 || mmi.sic.length > 8) {
  1.2299 +        _sendMMIError(MMI_ERROR_KS_INVALID_PIN, mmiServiceCode);
  1.2300 +        return false;
  1.2301 +      }
  1.2302 +
  1.2303 +      return true;
  1.2304 +    }
  1.2305 +
  1.2306 +    let _isRadioAvailable = (function(mmiServiceCode) {
  1.2307 +      if (this.radioState !== GECKO_RADIOSTATE_READY) {
  1.2308 +        _sendMMIError(GECKO_ERROR_RADIO_NOT_AVAILABLE, mmiServiceCode);
  1.2309 +        return false;
  1.2310 +      }
  1.2311 +      return true;
  1.2312 +    }).bind(this);
  1.2313 +
  1.2314 +    // If we couldn't parse the MMI code, we'll send it as an USSD request.
  1.2315 +    if (mmi === null) {
  1.2316 +      if (this._ussdSession) {
  1.2317 +        if (!_isRadioAvailable(MMI_KS_SC_USSD)) {
  1.2318 +          return;
  1.2319 +        }
  1.2320 +        options.ussd = mmiString;
  1.2321 +        this.sendUSSD(options);
  1.2322 +        return;
  1.2323 +      }
  1.2324 +
  1.2325 +      _sendMMIError(MMI_ERROR_KS_ERROR);
  1.2326 +      return;
  1.2327 +    }
  1.2328 +
  1.2329 +    if (DEBUG) {
  1.2330 +      this.context.debug("MMI " + JSON.stringify(mmi));
  1.2331 +    }
  1.2332 +
  1.2333 +    // We check if the MMI service code is supported and in that case we
  1.2334 +    // trigger the appropriate RIL request if possible.
  1.2335 +    let sc = mmi.serviceCode;
  1.2336 +    switch (sc) {
  1.2337 +      // Call forwarding
  1.2338 +      case MMI_SC_CFU:
  1.2339 +      case MMI_SC_CF_BUSY:
  1.2340 +      case MMI_SC_CF_NO_REPLY:
  1.2341 +      case MMI_SC_CF_NOT_REACHABLE:
  1.2342 +      case MMI_SC_CF_ALL:
  1.2343 +      case MMI_SC_CF_ALL_CONDITIONAL:
  1.2344 +        if (!_isRadioAvailable(MMI_KS_SC_CALL_FORWARDING)) {
  1.2345 +          return;
  1.2346 +        }
  1.2347 +        // Call forwarding requires at least an action, given by the MMI
  1.2348 +        // procedure, and a reason, given by the MMI service code, but there
  1.2349 +        // is no way that we get this far without a valid procedure or service
  1.2350 +        // code.
  1.2351 +        options.mmiServiceCode = MMI_KS_SC_CALL_FORWARDING;
  1.2352 +        options.action = MMI_PROC_TO_CF_ACTION[mmi.procedure];
  1.2353 +        options.reason = MMI_SC_TO_CF_REASON[sc];
  1.2354 +        options.number = mmi.sia;
  1.2355 +        options.serviceClass = this._siToServiceClass(mmi.sib);
  1.2356 +        if (options.action == CALL_FORWARD_ACTION_QUERY_STATUS) {
  1.2357 +          this.queryCallForwardStatus(options);
  1.2358 +          return;
  1.2359 +        }
  1.2360 +
  1.2361 +        options.isSetCallForward = true;
  1.2362 +        options.timeSeconds = mmi.sic;
  1.2363 +        this.setCallForward(options);
  1.2364 +        return;
  1.2365 +
  1.2366 +      // Change the current ICC PIN number.
  1.2367 +      case MMI_SC_PIN:
  1.2368 +        // As defined in TS.122.030 6.6.2 to change the ICC PIN we should expect
  1.2369 +        // an MMI code of the form **04*OLD_PIN*NEW_PIN*NEW_PIN#, where old PIN
  1.2370 +        // should be entered as the SIA parameter and the new PIN as SIB and
  1.2371 +        // SIC.
  1.2372 +        if (!_isRadioAvailable(MMI_KS_SC_PIN) ||
  1.2373 +            !_isValidPINPUKRequest(MMI_KS_SC_PIN)) {
  1.2374 +          return;
  1.2375 +        }
  1.2376 +
  1.2377 +        options.mmiServiceCode = MMI_KS_SC_PIN;
  1.2378 +        options.pin = mmi.sia;
  1.2379 +        options.newPin = mmi.sib;
  1.2380 +        this.changeICCPIN(options);
  1.2381 +        return;
  1.2382 +
  1.2383 +      // Change the current ICC PIN2 number.
  1.2384 +      case MMI_SC_PIN2:
  1.2385 +        // As defined in TS.122.030 6.6.2 to change the ICC PIN2 we should
  1.2386 +        // enter and MMI code of the form **042*OLD_PIN2*NEW_PIN2*NEW_PIN2#,
  1.2387 +        // where the old PIN2 should be entered as the SIA parameter and the
  1.2388 +        // new PIN2 as SIB and SIC.
  1.2389 +        if (!_isRadioAvailable(MMI_KS_SC_PIN2) ||
  1.2390 +            !_isValidPINPUKRequest(MMI_KS_SC_PIN2)) {
  1.2391 +          return;
  1.2392 +        }
  1.2393 +
  1.2394 +        options.mmiServiceCode = MMI_KS_SC_PIN2;
  1.2395 +        options.pin = mmi.sia;
  1.2396 +        options.newPin = mmi.sib;
  1.2397 +        this.changeICCPIN2(options);
  1.2398 +        return;
  1.2399 +
  1.2400 +      // Unblock ICC PIN.
  1.2401 +      case MMI_SC_PUK:
  1.2402 +        // As defined in TS.122.030 6.6.3 to unblock the ICC PIN we should
  1.2403 +        // enter an MMI code of the form **05*PUK*NEW_PIN*NEW_PIN#, where PUK
  1.2404 +        // should be entered as the SIA parameter and the new PIN as SIB and
  1.2405 +        // SIC.
  1.2406 +        if (!_isRadioAvailable(MMI_KS_SC_PUK) ||
  1.2407 +            !_isValidPINPUKRequest(MMI_KS_SC_PUK)) {
  1.2408 +          return;
  1.2409 +        }
  1.2410 +
  1.2411 +        options.mmiServiceCode = MMI_KS_SC_PUK;
  1.2412 +        options.puk = mmi.sia;
  1.2413 +        options.newPin = mmi.sib;
  1.2414 +        this.enterICCPUK(options);
  1.2415 +        return;
  1.2416 +
  1.2417 +      // Unblock ICC PIN2.
  1.2418 +      case MMI_SC_PUK2:
  1.2419 +        // As defined in TS.122.030 6.6.3 to unblock the ICC PIN2 we should
  1.2420 +        // enter an MMI code of the form **052*PUK2*NEW_PIN2*NEW_PIN2#, where
  1.2421 +        // PUK2 should be entered as the SIA parameter and the new PIN2 as SIB
  1.2422 +        // and SIC.
  1.2423 +        if (!_isRadioAvailable(MMI_KS_SC_PUK2) ||
  1.2424 +            !_isValidPINPUKRequest(MMI_KS_SC_PUK2)) {
  1.2425 +          return;
  1.2426 +        }
  1.2427 +
  1.2428 +        options.mmiServiceCode = MMI_KS_SC_PUK2;
  1.2429 +        options.puk = mmi.sia;
  1.2430 +        options.newPin = mmi.sib;
  1.2431 +        this.enterICCPUK2(options);
  1.2432 +        return;
  1.2433 +
  1.2434 +      // IMEI
  1.2435 +      case MMI_SC_IMEI:
  1.2436 +        // A device's IMEI can't change, so we only need to request it once.
  1.2437 +        if (this.IMEI == null) {
  1.2438 +          this.getIMEI(options);
  1.2439 +          return;
  1.2440 +        }
  1.2441 +        // If we already had the device's IMEI, we just send it to chrome.
  1.2442 +        options.mmiServiceCode = MMI_KS_SC_IMEI;
  1.2443 +        options.success = true;
  1.2444 +        options.statusMessage = this.IMEI;
  1.2445 +        this.sendChromeMessage(options);
  1.2446 +        return;
  1.2447 +
  1.2448 +      // CLIP
  1.2449 +      case MMI_SC_CLIP:
  1.2450 +        options.mmiServiceCode = MMI_KS_SC_CLIP;
  1.2451 +        options.procedure = mmi.procedure;
  1.2452 +        if (options.procedure === MMI_PROCEDURE_INTERROGATION) {
  1.2453 +          this.queryCLIP(options);
  1.2454 +        } else {
  1.2455 +          _sendMMIError(MMI_ERROR_KS_NOT_SUPPORTED, MMI_KS_SC_CLIP);
  1.2456 +        }
  1.2457 +        return;
  1.2458 +
  1.2459 +      // CLIR (non-temporary ones)
  1.2460 +      // TODO: Both dial() and sendMMI() functions should be unified at some
  1.2461 +      // point in the future. In the mean time we handle temporary CLIR MMI
  1.2462 +      // commands through the dial() function. Please see bug 889737.
  1.2463 +      case MMI_SC_CLIR:
  1.2464 +        options.mmiServiceCode = MMI_KS_SC_CLIR;
  1.2465 +        options.procedure = mmi.procedure;
  1.2466 +        switch (options.procedure) {
  1.2467 +          case MMI_PROCEDURE_INTERROGATION:
  1.2468 +            this.getCLIR(options);
  1.2469 +            return;
  1.2470 +          case MMI_PROCEDURE_ACTIVATION:
  1.2471 +            options.clirMode = CLIR_INVOCATION;
  1.2472 +            break;
  1.2473 +          case MMI_PROCEDURE_DEACTIVATION:
  1.2474 +            options.clirMode = CLIR_SUPPRESSION;
  1.2475 +            break;
  1.2476 +          default:
  1.2477 +            _sendMMIError(MMI_ERROR_KS_NOT_SUPPORTED, MMI_KS_SC_CLIR);
  1.2478 +            return;
  1.2479 +        }
  1.2480 +        options.isSetCLIR = true;
  1.2481 +        this.setCLIR(options);
  1.2482 +        return;
  1.2483 +
  1.2484 +      // Call barring
  1.2485 +      case MMI_SC_BAOC:
  1.2486 +      case MMI_SC_BAOIC:
  1.2487 +      case MMI_SC_BAOICxH:
  1.2488 +      case MMI_SC_BAIC:
  1.2489 +      case MMI_SC_BAICr:
  1.2490 +      case MMI_SC_BA_ALL:
  1.2491 +      case MMI_SC_BA_MO:
  1.2492 +      case MMI_SC_BA_MT:
  1.2493 +        options.mmiServiceCode = MMI_KS_SC_CALL_BARRING;
  1.2494 +        options.password = mmi.sia || "";
  1.2495 +        options.serviceClass = this._siToServiceClass(mmi.sib);
  1.2496 +        options.facility = MMI_SC_TO_CB_FACILITY[sc];
  1.2497 +        options.procedure = mmi.procedure;
  1.2498 +        if (mmi.procedure === MMI_PROCEDURE_INTERROGATION) {
  1.2499 +          this.queryICCFacilityLock(options);
  1.2500 +          return;
  1.2501 +        }
  1.2502 +        if (mmi.procedure === MMI_PROCEDURE_ACTIVATION) {
  1.2503 +          options.enabled = 1;
  1.2504 +        } else if (mmi.procedure === MMI_PROCEDURE_DEACTIVATION) {
  1.2505 +          options.enabled = 0;
  1.2506 +        } else {
  1.2507 +          _sendMMIError(MMI_ERROR_KS_NOT_SUPPORTED, MMI_KS_SC_CALL_BARRING);
  1.2508 +          return;
  1.2509 +        }
  1.2510 +        this.setICCFacilityLock(options);
  1.2511 +        return;
  1.2512 +
  1.2513 +      // Call waiting
  1.2514 +      case MMI_SC_CALL_WAITING:
  1.2515 +        if (!_isRadioAvailable(MMI_KS_SC_CALL_WAITING)) {
  1.2516 +          return;
  1.2517 +        }
  1.2518 +
  1.2519 +        options.mmiServiceCode = MMI_KS_SC_CALL_WAITING;
  1.2520 +
  1.2521 +        if (mmi.procedure === MMI_PROCEDURE_INTERROGATION) {
  1.2522 +          this._handleQueryMMICallWaiting(options);
  1.2523 +          return;
  1.2524 +        }
  1.2525 +
  1.2526 +        if (mmi.procedure === MMI_PROCEDURE_ACTIVATION) {
  1.2527 +          options.enabled = true;
  1.2528 +        } else if (mmi.procedure === MMI_PROCEDURE_DEACTIVATION) {
  1.2529 +          options.enabled = false;
  1.2530 +        } else {
  1.2531 +          _sendMMIError(MMI_ERROR_KS_NOT_SUPPORTED, MMI_KS_SC_CALL_WAITING);
  1.2532 +          return;
  1.2533 +        }
  1.2534 +
  1.2535 +        options.serviceClass = this._siToServiceClass(mmi.sia);
  1.2536 +        this._handleSetMMICallWaiting(options);
  1.2537 +        return;
  1.2538 +    }
  1.2539 +
  1.2540 +    // If the MMI code is not a known code and is a recognized USSD request,
  1.2541 +    // it shall still be sent as a USSD request.
  1.2542 +    if (mmi.fullMMI) {
  1.2543 +      if (!_isRadioAvailable(MMI_KS_SC_USSD)) {
  1.2544 +        return;
  1.2545 +      }
  1.2546 +
  1.2547 +      options.ussd = mmi.fullMMI;
  1.2548 +      options.mmiServiceCode = MMI_KS_SC_USSD;
  1.2549 +      this.sendUSSD(options);
  1.2550 +      return;
  1.2551 +    }
  1.2552 +
  1.2553 +    // At this point, the MMI string is considered as not valid MMI code and
  1.2554 +    // not valid USSD code.
  1.2555 +    _sendMMIError(MMI_ERROR_KS_ERROR);
  1.2556 +  },
  1.2557 +
  1.2558 +  /**
  1.2559 +   * Send USSD.
  1.2560 +   *
  1.2561 +   * @param ussd
  1.2562 +   *        String containing the USSD code.
  1.2563 +   *
  1.2564 +   */
  1.2565 +   sendUSSD: function(options) {
  1.2566 +     let Buf = this.context.Buf;
  1.2567 +     Buf.newParcel(REQUEST_SEND_USSD, options);
  1.2568 +     Buf.writeString(options.ussd);
  1.2569 +     Buf.sendParcel();
  1.2570 +   },
  1.2571 +
  1.2572 +  /**
  1.2573 +   * Cancel pending USSD.
  1.2574 +   */
  1.2575 +   cancelUSSD: function(options) {
  1.2576 +     options.mmiServiceCode = MMI_KS_SC_USSD;
  1.2577 +     this.context.Buf.simpleRequest(REQUEST_CANCEL_USSD, options);
  1.2578 +   },
  1.2579 +
  1.2580 +  /**
  1.2581 +   * Queries current call forward rules.
  1.2582 +   *
  1.2583 +   * @param reason
  1.2584 +   *        One of nsIDOMMozMobileCFInfo.CALL_FORWARD_REASON_* constants.
  1.2585 +   * @param serviceClass
  1.2586 +   *        One of ICC_SERVICE_CLASS_* constants.
  1.2587 +   * @param number
  1.2588 +   *        Phone number of forwarding address.
  1.2589 +   */
  1.2590 +  queryCallForwardStatus: function(options) {
  1.2591 +    let Buf = this.context.Buf;
  1.2592 +    let number = options.number || "";
  1.2593 +    Buf.newParcel(REQUEST_QUERY_CALL_FORWARD_STATUS, options);
  1.2594 +    Buf.writeInt32(CALL_FORWARD_ACTION_QUERY_STATUS);
  1.2595 +    Buf.writeInt32(options.reason);
  1.2596 +    Buf.writeInt32(options.serviceClass || ICC_SERVICE_CLASS_NONE);
  1.2597 +    Buf.writeInt32(this._toaFromString(number));
  1.2598 +    Buf.writeString(number);
  1.2599 +    Buf.writeInt32(0);
  1.2600 +    Buf.sendParcel();
  1.2601 +  },
  1.2602 +
  1.2603 +  /**
  1.2604 +   * Configures call forward rule.
  1.2605 +   *
  1.2606 +   * @param action
  1.2607 +   *        One of nsIDOMMozMobileCFInfo.CALL_FORWARD_ACTION_* constants.
  1.2608 +   * @param reason
  1.2609 +   *        One of nsIDOMMozMobileCFInfo.CALL_FORWARD_REASON_* constants.
  1.2610 +   * @param serviceClass
  1.2611 +   *        One of ICC_SERVICE_CLASS_* constants.
  1.2612 +   * @param number
  1.2613 +   *        Phone number of forwarding address.
  1.2614 +   * @param timeSeconds
  1.2615 +   *        Time in seconds to wait beforec all is forwarded.
  1.2616 +   */
  1.2617 +  setCallForward: function(options) {
  1.2618 +    let Buf = this.context.Buf;
  1.2619 +    Buf.newParcel(REQUEST_SET_CALL_FORWARD, options);
  1.2620 +    Buf.writeInt32(options.action);
  1.2621 +    Buf.writeInt32(options.reason);
  1.2622 +    Buf.writeInt32(options.serviceClass);
  1.2623 +    Buf.writeInt32(this._toaFromString(options.number));
  1.2624 +    Buf.writeString(options.number);
  1.2625 +    Buf.writeInt32(options.timeSeconds);
  1.2626 +    Buf.sendParcel();
  1.2627 +  },
  1.2628 +
  1.2629 +  /**
  1.2630 +   * Queries current call barring rules.
  1.2631 +   *
  1.2632 +   * @param program
  1.2633 +   *        One of nsIDOMMozMobileConnection.CALL_BARRING_PROGRAM_* constants.
  1.2634 +   * @param serviceClass
  1.2635 +   *        One of ICC_SERVICE_CLASS_* constants.
  1.2636 +   */
  1.2637 +  queryCallBarringStatus: function(options) {
  1.2638 +    options.facility = CALL_BARRING_PROGRAM_TO_FACILITY[options.program];
  1.2639 +    options.password = ""; // For query no need to provide it.
  1.2640 +    this.queryICCFacilityLock(options);
  1.2641 +  },
  1.2642 +
  1.2643 +  /**
  1.2644 +   * Configures call barring rule.
  1.2645 +   *
  1.2646 +   * @param program
  1.2647 +   *        One of nsIDOMMozMobileConnection.CALL_BARRING_PROGRAM_* constants.
  1.2648 +   * @param enabled
  1.2649 +   *        Enable or disable the call barring.
  1.2650 +   * @param password
  1.2651 +   *        Barring password.
  1.2652 +   * @param serviceClass
  1.2653 +   *        One of ICC_SERVICE_CLASS_* constants.
  1.2654 +   */
  1.2655 +  setCallBarring: function(options) {
  1.2656 +    options.facility = CALL_BARRING_PROGRAM_TO_FACILITY[options.program];
  1.2657 +    this.setICCFacilityLock(options);
  1.2658 +  },
  1.2659 +
  1.2660 +  /**
  1.2661 +   * Change call barring facility password.
  1.2662 +   *
  1.2663 +   * @param pin
  1.2664 +   *        Old password.
  1.2665 +   * @param newPin
  1.2666 +   *        New password.
  1.2667 +   */
  1.2668 +  changeCallBarringPassword: function(options) {
  1.2669 +    let Buf = this.context.Buf;
  1.2670 +    Buf.newParcel(REQUEST_CHANGE_BARRING_PASSWORD, options);
  1.2671 +    Buf.writeInt32(3);
  1.2672 +    // Set facility to ICC_CB_FACILITY_BA_ALL by following TS.22.030 clause
  1.2673 +    // 6.5.4 and Table B.1.
  1.2674 +    Buf.writeString(ICC_CB_FACILITY_BA_ALL);
  1.2675 +    Buf.writeString(options.pin);
  1.2676 +    Buf.writeString(options.newPin);
  1.2677 +    Buf.sendParcel();
  1.2678 +  },
  1.2679 +
  1.2680 +  /**
  1.2681 +   * Handle STK CALL_SET_UP request.
  1.2682 +   *
  1.2683 +   * @param hasConfirmed
  1.2684 +   *        Does use have confirmed the call requested from ICC?
  1.2685 +   */
  1.2686 +  stkHandleCallSetup: function(options) {
  1.2687 +     let Buf = this.context.Buf;
  1.2688 +     Buf.newParcel(REQUEST_STK_HANDLE_CALL_SETUP_REQUESTED_FROM_SIM);
  1.2689 +     Buf.writeInt32(1);
  1.2690 +     Buf.writeInt32(options.hasConfirmed ? 1 : 0);
  1.2691 +     Buf.sendParcel();
  1.2692 +  },
  1.2693 +
  1.2694 +  /**
  1.2695 +   * Send STK Profile Download.
  1.2696 +   *
  1.2697 +   * @param profile Profile supported by ME.
  1.2698 +   */
  1.2699 +  sendStkTerminalProfile: function(profile) {
  1.2700 +    let Buf = this.context.Buf;
  1.2701 +    let GsmPDUHelper = this.context.GsmPDUHelper;
  1.2702 +
  1.2703 +    Buf.newParcel(REQUEST_STK_SET_PROFILE);
  1.2704 +    Buf.writeInt32(profile.length * 2);
  1.2705 +    for (let i = 0; i < profile.length; i++) {
  1.2706 +      GsmPDUHelper.writeHexOctet(profile[i]);
  1.2707 +    }
  1.2708 +    Buf.writeInt32(0);
  1.2709 +    Buf.sendParcel();
  1.2710 +  },
  1.2711 +
  1.2712 +  /**
  1.2713 +   * Send STK terminal response.
  1.2714 +   *
  1.2715 +   * @param command
  1.2716 +   * @param deviceIdentities
  1.2717 +   * @param resultCode
  1.2718 +   * @param [optional] itemIdentifier
  1.2719 +   * @param [optional] input
  1.2720 +   * @param [optional] isYesNo
  1.2721 +   * @param [optional] hasConfirmed
  1.2722 +   * @param [optional] localInfo
  1.2723 +   * @param [optional] timer
  1.2724 +   */
  1.2725 +  sendStkTerminalResponse: function(response) {
  1.2726 +    if (response.hasConfirmed !== undefined) {
  1.2727 +      this.stkHandleCallSetup(response);
  1.2728 +      return;
  1.2729 +    }
  1.2730 +
  1.2731 +    let Buf = this.context.Buf;
  1.2732 +    let ComprehensionTlvHelper = this.context.ComprehensionTlvHelper;
  1.2733 +    let GsmPDUHelper = this.context.GsmPDUHelper;
  1.2734 +
  1.2735 +    let command = response.command;
  1.2736 +    Buf.newParcel(REQUEST_STK_SEND_TERMINAL_RESPONSE);
  1.2737 +
  1.2738 +    // 1st mark for Parcel size
  1.2739 +    Buf.startCalOutgoingSize(function(size) {
  1.2740 +      // Parcel size is in string length, which costs 2 uint8 per char.
  1.2741 +      Buf.writeInt32(size / 2);
  1.2742 +    });
  1.2743 +
  1.2744 +    // Command Details
  1.2745 +    GsmPDUHelper.writeHexOctet(COMPREHENSIONTLV_TAG_COMMAND_DETAILS |
  1.2746 +                               COMPREHENSIONTLV_FLAG_CR);
  1.2747 +    GsmPDUHelper.writeHexOctet(3);
  1.2748 +    if (response.command) {
  1.2749 +      GsmPDUHelper.writeHexOctet(command.commandNumber);
  1.2750 +      GsmPDUHelper.writeHexOctet(command.typeOfCommand);
  1.2751 +      GsmPDUHelper.writeHexOctet(command.commandQualifier);
  1.2752 +    } else {
  1.2753 +      GsmPDUHelper.writeHexOctet(0x00);
  1.2754 +      GsmPDUHelper.writeHexOctet(0x00);
  1.2755 +      GsmPDUHelper.writeHexOctet(0x00);
  1.2756 +    }
  1.2757 +
  1.2758 +    // Device Identifier
  1.2759 +    // According to TS102.223/TS31.111 section 6.8 Structure of
  1.2760 +    // TERMINAL RESPONSE, "For all SIMPLE-TLV objects with Min=N,
  1.2761 +    // the ME should set the CR(comprehension required) flag to
  1.2762 +    // comprehension not required.(CR=0)"
  1.2763 +    // Since DEVICE_IDENTITIES and DURATION TLVs have Min=N,
  1.2764 +    // the CR flag is not set.
  1.2765 +    GsmPDUHelper.writeHexOctet(COMPREHENSIONTLV_TAG_DEVICE_ID);
  1.2766 +    GsmPDUHelper.writeHexOctet(2);
  1.2767 +    GsmPDUHelper.writeHexOctet(STK_DEVICE_ID_ME);
  1.2768 +    GsmPDUHelper.writeHexOctet(STK_DEVICE_ID_SIM);
  1.2769 +
  1.2770 +    // Result
  1.2771 +    GsmPDUHelper.writeHexOctet(COMPREHENSIONTLV_TAG_RESULT |
  1.2772 +                               COMPREHENSIONTLV_FLAG_CR);
  1.2773 +    GsmPDUHelper.writeHexOctet(1);
  1.2774 +    GsmPDUHelper.writeHexOctet(response.resultCode);
  1.2775 +
  1.2776 +    // Item Identifier
  1.2777 +    if (response.itemIdentifier != null) {
  1.2778 +      GsmPDUHelper.writeHexOctet(COMPREHENSIONTLV_TAG_ITEM_ID |
  1.2779 +                                 COMPREHENSIONTLV_FLAG_CR);
  1.2780 +      GsmPDUHelper.writeHexOctet(1);
  1.2781 +      GsmPDUHelper.writeHexOctet(response.itemIdentifier);
  1.2782 +    }
  1.2783 +
  1.2784 +    // No need to process Text data if user requests help information.
  1.2785 +    if (response.resultCode != STK_RESULT_HELP_INFO_REQUIRED) {
  1.2786 +      let text;
  1.2787 +      if (response.isYesNo !== undefined) {
  1.2788 +        // GET_INKEY
  1.2789 +        // When the ME issues a successful TERMINAL RESPONSE for a GET INKEY
  1.2790 +        // ("Yes/No") command with command qualifier set to "Yes/No", it shall
  1.2791 +        // supply the value '01' when the answer is "positive" and the value
  1.2792 +        // '00' when the answer is "negative" in the Text string data object.
  1.2793 +        text = response.isYesNo ? 0x01 : 0x00;
  1.2794 +      } else {
  1.2795 +        text = response.input;
  1.2796 +      }
  1.2797 +
  1.2798 +      if (text) {
  1.2799 +        GsmPDUHelper.writeHexOctet(COMPREHENSIONTLV_TAG_TEXT_STRING |
  1.2800 +                                   COMPREHENSIONTLV_FLAG_CR);
  1.2801 +
  1.2802 +        // 2nd mark for text length
  1.2803 +        Buf.startCalOutgoingSize(function(size) {
  1.2804 +          // Text length is in number of hexOctets, which costs 4 uint8 per hexOctet.
  1.2805 +          GsmPDUHelper.writeHexOctet(size / 4);
  1.2806 +        });
  1.2807 +
  1.2808 +        let coding = command.options.isUCS2 ?
  1.2809 +                       STK_TEXT_CODING_UCS2 :
  1.2810 +                       (command.options.isPacked ?
  1.2811 +                          STK_TEXT_CODING_GSM_7BIT_PACKED :
  1.2812 +                          STK_TEXT_CODING_GSM_8BIT);
  1.2813 +        GsmPDUHelper.writeHexOctet(coding);
  1.2814 +
  1.2815 +        // Write Text String.
  1.2816 +        switch (coding) {
  1.2817 +          case STK_TEXT_CODING_UCS2:
  1.2818 +            GsmPDUHelper.writeUCS2String(text);
  1.2819 +            break;
  1.2820 +          case STK_TEXT_CODING_GSM_7BIT_PACKED:
  1.2821 +            GsmPDUHelper.writeStringAsSeptets(text, 0, 0, 0);
  1.2822 +            break;
  1.2823 +          case STK_TEXT_CODING_GSM_8BIT:
  1.2824 +            for (let i = 0; i < text.length; i++) {
  1.2825 +              GsmPDUHelper.writeHexOctet(text.charCodeAt(i));
  1.2826 +            }
  1.2827 +            break;
  1.2828 +        }
  1.2829 +
  1.2830 +        // Calculate and write text length to 2nd mark
  1.2831 +        Buf.stopCalOutgoingSize();
  1.2832 +      }
  1.2833 +    }
  1.2834 +
  1.2835 +    // Local Information
  1.2836 +    if (response.localInfo) {
  1.2837 +      let localInfo = response.localInfo;
  1.2838 +
  1.2839 +      // Location Infomation
  1.2840 +      if (localInfo.locationInfo) {
  1.2841 +        ComprehensionTlvHelper.writeLocationInfoTlv(localInfo.locationInfo);
  1.2842 +      }
  1.2843 +
  1.2844 +      // IMEI
  1.2845 +      if (localInfo.imei != null) {
  1.2846 +        let imei = localInfo.imei;
  1.2847 +        if (imei.length == 15) {
  1.2848 +          imei = imei + "0";
  1.2849 +        }
  1.2850 +
  1.2851 +        GsmPDUHelper.writeHexOctet(COMPREHENSIONTLV_TAG_IMEI);
  1.2852 +        GsmPDUHelper.writeHexOctet(8);
  1.2853 +        for (let i = 0; i < imei.length / 2; i++) {
  1.2854 +          GsmPDUHelper.writeHexOctet(parseInt(imei.substr(i * 2, 2), 16));
  1.2855 +        }
  1.2856 +      }
  1.2857 +
  1.2858 +      // Date and Time Zone
  1.2859 +      if (localInfo.date != null) {
  1.2860 +        ComprehensionTlvHelper.writeDateTimeZoneTlv(localInfo.date);
  1.2861 +      }
  1.2862 +
  1.2863 +      // Language
  1.2864 +      if (localInfo.language) {
  1.2865 +        ComprehensionTlvHelper.writeLanguageTlv(localInfo.language);
  1.2866 +      }
  1.2867 +    }
  1.2868 +
  1.2869 +    // Timer
  1.2870 +    if (response.timer) {
  1.2871 +      let timer = response.timer;
  1.2872 +
  1.2873 +      if (timer.timerId) {
  1.2874 +        GsmPDUHelper.writeHexOctet(COMPREHENSIONTLV_TAG_TIMER_IDENTIFIER);
  1.2875 +        GsmPDUHelper.writeHexOctet(1);
  1.2876 +        GsmPDUHelper.writeHexOctet(timer.timerId);
  1.2877 +      }
  1.2878 +
  1.2879 +      if (timer.timerValue) {
  1.2880 +        ComprehensionTlvHelper.writeTimerValueTlv(timer.timerValue, false);
  1.2881 +      }
  1.2882 +    }
  1.2883 +
  1.2884 +    // Calculate and write Parcel size to 1st mark
  1.2885 +    Buf.stopCalOutgoingSize();
  1.2886 +
  1.2887 +    Buf.writeInt32(0);
  1.2888 +    Buf.sendParcel();
  1.2889 +  },
  1.2890 +
  1.2891 +  /**
  1.2892 +   * Send STK Envelope(Menu Selection) command.
  1.2893 +   *
  1.2894 +   * @param itemIdentifier
  1.2895 +   * @param helpRequested
  1.2896 +   */
  1.2897 +  sendStkMenuSelection: function(command) {
  1.2898 +    command.tag = BER_MENU_SELECTION_TAG;
  1.2899 +    command.deviceId = {
  1.2900 +      sourceId :STK_DEVICE_ID_KEYPAD,
  1.2901 +      destinationId: STK_DEVICE_ID_SIM
  1.2902 +    };
  1.2903 +    this.sendICCEnvelopeCommand(command);
  1.2904 +  },
  1.2905 +
  1.2906 +  /**
  1.2907 +   * Send STK Envelope(Timer Expiration) command.
  1.2908 +   *
  1.2909 +   * @param timer
  1.2910 +   */
  1.2911 +  sendStkTimerExpiration: function(command) {
  1.2912 +    command.tag = BER_TIMER_EXPIRATION_TAG;
  1.2913 +    command.deviceId = {
  1.2914 +      sourceId: STK_DEVICE_ID_ME,
  1.2915 +      destinationId: STK_DEVICE_ID_SIM
  1.2916 +    };
  1.2917 +    command.timerId = command.timer.timerId;
  1.2918 +    command.timerValue = command.timer.timerValue;
  1.2919 +    this.sendICCEnvelopeCommand(command);
  1.2920 +  },
  1.2921 +
  1.2922 +  /**
  1.2923 +   * Send STK Envelope(Event Download) command.
  1.2924 +   * @param event
  1.2925 +   */
  1.2926 +  sendStkEventDownload: function(command) {
  1.2927 +    command.tag = BER_EVENT_DOWNLOAD_TAG;
  1.2928 +    command.eventList = command.event.eventType;
  1.2929 +    switch (command.eventList) {
  1.2930 +      case STK_EVENT_TYPE_LOCATION_STATUS:
  1.2931 +        command.deviceId = {
  1.2932 +          sourceId :STK_DEVICE_ID_ME,
  1.2933 +          destinationId: STK_DEVICE_ID_SIM
  1.2934 +        };
  1.2935 +        command.locationStatus = command.event.locationStatus;
  1.2936 +        // Location info should only be provided when locationStatus is normal.
  1.2937 +        if (command.locationStatus == STK_SERVICE_STATE_NORMAL) {
  1.2938 +          command.locationInfo = command.event.locationInfo;
  1.2939 +        }
  1.2940 +        break;
  1.2941 +      case STK_EVENT_TYPE_MT_CALL:
  1.2942 +        command.deviceId = {
  1.2943 +          sourceId: STK_DEVICE_ID_NETWORK,
  1.2944 +          destinationId: STK_DEVICE_ID_SIM
  1.2945 +        };
  1.2946 +        command.transactionId = 0;
  1.2947 +        command.address = command.event.number;
  1.2948 +        break;
  1.2949 +      case STK_EVENT_TYPE_CALL_DISCONNECTED:
  1.2950 +        command.cause = command.event.error;
  1.2951 +        // Fall through.
  1.2952 +      case STK_EVENT_TYPE_CALL_CONNECTED:
  1.2953 +        command.deviceId = {
  1.2954 +          sourceId: (command.event.isIssuedByRemote ?
  1.2955 +                     STK_DEVICE_ID_NETWORK : STK_DEVICE_ID_ME),
  1.2956 +          destinationId: STK_DEVICE_ID_SIM
  1.2957 +        };
  1.2958 +        command.transactionId = 0;
  1.2959 +        break;
  1.2960 +      case STK_EVENT_TYPE_USER_ACTIVITY:
  1.2961 +        command.deviceId = {
  1.2962 +          sourceId: STK_DEVICE_ID_ME,
  1.2963 +          destinationId: STK_DEVICE_ID_SIM
  1.2964 +        };
  1.2965 +        break;
  1.2966 +      case STK_EVENT_TYPE_IDLE_SCREEN_AVAILABLE:
  1.2967 +        command.deviceId = {
  1.2968 +          sourceId: STK_DEVICE_ID_DISPLAY,
  1.2969 +          destinationId: STK_DEVICE_ID_SIM
  1.2970 +        };
  1.2971 +        break;
  1.2972 +      case STK_EVENT_TYPE_LANGUAGE_SELECTION:
  1.2973 +        command.deviceId = {
  1.2974 +          sourceId: STK_DEVICE_ID_ME,
  1.2975 +          destinationId: STK_DEVICE_ID_SIM
  1.2976 +        };
  1.2977 +        command.language = command.event.language;
  1.2978 +        break;
  1.2979 +      case STK_EVENT_TYPE_BROWSER_TERMINATION:
  1.2980 +        command.deviceId = {
  1.2981 +          sourceId: STK_DEVICE_ID_ME,
  1.2982 +          destinationId: STK_DEVICE_ID_SIM
  1.2983 +        };
  1.2984 +        command.terminationCause = command.event.terminationCause;
  1.2985 +        break;
  1.2986 +    }
  1.2987 +    this.sendICCEnvelopeCommand(command);
  1.2988 +  },
  1.2989 +
  1.2990 +  /**
  1.2991 +   * Send REQUEST_STK_SEND_ENVELOPE_COMMAND to ICC.
  1.2992 +   *
  1.2993 +   * @param tag
  1.2994 +   * @patam deviceId
  1.2995 +   * @param [optioanl] itemIdentifier
  1.2996 +   * @param [optional] helpRequested
  1.2997 +   * @param [optional] eventList
  1.2998 +   * @param [optional] locationStatus
  1.2999 +   * @param [optional] locationInfo
  1.3000 +   * @param [optional] address
  1.3001 +   * @param [optional] transactionId
  1.3002 +   * @param [optional] cause
  1.3003 +   * @param [optional] timerId
  1.3004 +   * @param [optional] timerValue
  1.3005 +   * @param [optional] terminationCause
  1.3006 +   */
  1.3007 +  sendICCEnvelopeCommand: function(options) {
  1.3008 +    if (DEBUG) {
  1.3009 +      this.context.debug("Stk Envelope " + JSON.stringify(options));
  1.3010 +    }
  1.3011 +
  1.3012 +    let Buf = this.context.Buf;
  1.3013 +    let ComprehensionTlvHelper = this.context.ComprehensionTlvHelper;
  1.3014 +    let GsmPDUHelper = this.context.GsmPDUHelper;
  1.3015 +
  1.3016 +    Buf.newParcel(REQUEST_STK_SEND_ENVELOPE_COMMAND);
  1.3017 +
  1.3018 +    // 1st mark for Parcel size
  1.3019 +    Buf.startCalOutgoingSize(function(size) {
  1.3020 +      // Parcel size is in string length, which costs 2 uint8 per char.
  1.3021 +      Buf.writeInt32(size / 2);
  1.3022 +    });
  1.3023 +
  1.3024 +    // Write a BER-TLV
  1.3025 +    GsmPDUHelper.writeHexOctet(options.tag);
  1.3026 +    // 2nd mark for BER length
  1.3027 +    Buf.startCalOutgoingSize(function(size) {
  1.3028 +      // BER length is in number of hexOctets, which costs 4 uint8 per hexOctet.
  1.3029 +      GsmPDUHelper.writeHexOctet(size / 4);
  1.3030 +    });
  1.3031 +
  1.3032 +    // Device Identifies
  1.3033 +    GsmPDUHelper.writeHexOctet(COMPREHENSIONTLV_TAG_DEVICE_ID |
  1.3034 +                               COMPREHENSIONTLV_FLAG_CR);
  1.3035 +    GsmPDUHelper.writeHexOctet(2);
  1.3036 +    GsmPDUHelper.writeHexOctet(options.deviceId.sourceId);
  1.3037 +    GsmPDUHelper.writeHexOctet(options.deviceId.destinationId);
  1.3038 +
  1.3039 +    // Item Identifier
  1.3040 +    if (options.itemIdentifier != null) {
  1.3041 +      GsmPDUHelper.writeHexOctet(COMPREHENSIONTLV_TAG_ITEM_ID |
  1.3042 +                                 COMPREHENSIONTLV_FLAG_CR);
  1.3043 +      GsmPDUHelper.writeHexOctet(1);
  1.3044 +      GsmPDUHelper.writeHexOctet(options.itemIdentifier);
  1.3045 +    }
  1.3046 +
  1.3047 +    // Help Request
  1.3048 +    if (options.helpRequested) {
  1.3049 +      GsmPDUHelper.writeHexOctet(COMPREHENSIONTLV_TAG_HELP_REQUEST |
  1.3050 +                                 COMPREHENSIONTLV_FLAG_CR);
  1.3051 +      GsmPDUHelper.writeHexOctet(0);
  1.3052 +      // Help Request doesn't have value
  1.3053 +    }
  1.3054 +
  1.3055 +    // Event List
  1.3056 +    if (options.eventList != null) {
  1.3057 +      GsmPDUHelper.writeHexOctet(COMPREHENSIONTLV_TAG_EVENT_LIST |
  1.3058 +                                 COMPREHENSIONTLV_FLAG_CR);
  1.3059 +      GsmPDUHelper.writeHexOctet(1);
  1.3060 +      GsmPDUHelper.writeHexOctet(options.eventList);
  1.3061 +    }
  1.3062 +
  1.3063 +    // Location Status
  1.3064 +    if (options.locationStatus != null) {
  1.3065 +      let len = options.locationStatus.length;
  1.3066 +      GsmPDUHelper.writeHexOctet(COMPREHENSIONTLV_TAG_LOCATION_STATUS |
  1.3067 +                                 COMPREHENSIONTLV_FLAG_CR);
  1.3068 +      GsmPDUHelper.writeHexOctet(1);
  1.3069 +      GsmPDUHelper.writeHexOctet(options.locationStatus);
  1.3070 +    }
  1.3071 +
  1.3072 +    // Location Info
  1.3073 +    if (options.locationInfo) {
  1.3074 +      ComprehensionTlvHelper.writeLocationInfoTlv(options.locationInfo);
  1.3075 +    }
  1.3076 +
  1.3077 +    // Transaction Id
  1.3078 +    if (options.transactionId != null) {
  1.3079 +      GsmPDUHelper.writeHexOctet(COMPREHENSIONTLV_TAG_TRANSACTION_ID |
  1.3080 +                                 COMPREHENSIONTLV_FLAG_CR);
  1.3081 +      GsmPDUHelper.writeHexOctet(1);
  1.3082 +      GsmPDUHelper.writeHexOctet(options.transactionId);
  1.3083 +    }
  1.3084 +
  1.3085 +    // Address
  1.3086 +    if (options.address) {
  1.3087 +      GsmPDUHelper.writeHexOctet(COMPREHENSIONTLV_TAG_ADDRESS |
  1.3088 +                                 COMPREHENSIONTLV_FLAG_CR);
  1.3089 +      ComprehensionTlvHelper.writeLength(
  1.3090 +        Math.ceil(options.address.length/2) + 1 // address BCD + TON
  1.3091 +      );
  1.3092 +      this.context.ICCPDUHelper.writeDiallingNumber(options.address);
  1.3093 +    }
  1.3094 +
  1.3095 +    // Cause of disconnection.
  1.3096 +    if (options.cause != null) {
  1.3097 +      ComprehensionTlvHelper.writeCauseTlv(options.cause);
  1.3098 +    }
  1.3099 +
  1.3100 +    // Timer Identifier
  1.3101 +    if (options.timerId != null) {
  1.3102 +        GsmPDUHelper.writeHexOctet(COMPREHENSIONTLV_TAG_TIMER_IDENTIFIER |
  1.3103 +                                   COMPREHENSIONTLV_FLAG_CR);
  1.3104 +        GsmPDUHelper.writeHexOctet(1);
  1.3105 +        GsmPDUHelper.writeHexOctet(options.timerId);
  1.3106 +    }
  1.3107 +
  1.3108 +    // Timer Value
  1.3109 +    if (options.timerValue != null) {
  1.3110 +        ComprehensionTlvHelper.writeTimerValueTlv(options.timerValue, true);
  1.3111 +    }
  1.3112 +
  1.3113 +    // Language
  1.3114 +    if (options.language) {
  1.3115 +      ComprehensionTlvHelper.writeLanguageTlv(options.language);
  1.3116 +    }
  1.3117 +
  1.3118 +    // Browser Termination
  1.3119 +    if (options.terminationCause != null) {
  1.3120 +      GsmPDUHelper.writeHexOctet(COMPREHENSIONTLV_TAG_BROWSER_TERMINATION_CAUSE |
  1.3121 +                                 COMPREHENSIONTLV_FLAG_CR);
  1.3122 +      GsmPDUHelper.writeHexOctet(1);
  1.3123 +      GsmPDUHelper.writeHexOctet(options.terminationCause);
  1.3124 +    }
  1.3125 +
  1.3126 +    // Calculate and write BER length to 2nd mark
  1.3127 +    Buf.stopCalOutgoingSize();
  1.3128 +
  1.3129 +    // Calculate and write Parcel size to 1st mark
  1.3130 +    Buf.stopCalOutgoingSize();
  1.3131 +
  1.3132 +    Buf.writeInt32(0);
  1.3133 +    Buf.sendParcel();
  1.3134 +  },
  1.3135 +
  1.3136 +  /**
  1.3137 +   * Check a given number against the list of emergency numbers provided by the RIL.
  1.3138 +   *
  1.3139 +   * @param number
  1.3140 +   *        The number to look up.
  1.3141 +   */
  1.3142 +   _isEmergencyNumber: function(number) {
  1.3143 +     // Check ril provided numbers first.
  1.3144 +     let numbers = RIL_EMERGENCY_NUMBERS;
  1.3145 +
  1.3146 +     if (numbers) {
  1.3147 +       numbers = numbers.split(",");
  1.3148 +     } else {
  1.3149 +       // No ecclist system property, so use our own list.
  1.3150 +       numbers = DEFAULT_EMERGENCY_NUMBERS;
  1.3151 +     }
  1.3152 +
  1.3153 +     return numbers.indexOf(number) != -1;
  1.3154 +   },
  1.3155 +
  1.3156 +   /**
  1.3157 +    * Checks whether to temporarily suppress caller id for the call.
  1.3158 +    *
  1.3159 +    * @param mmi
  1.3160 +    *        MMI full object.
  1.3161 +    */
  1.3162 +   _isTemporaryModeCLIR: function(mmi) {
  1.3163 +     return (mmi &&
  1.3164 +             mmi.serviceCode == MMI_SC_CLIR &&
  1.3165 +             mmi.dialNumber &&
  1.3166 +             (mmi.procedure == MMI_PROCEDURE_ACTIVATION ||
  1.3167 +              mmi.procedure == MMI_PROCEDURE_DEACTIVATION));
  1.3168 +   },
  1.3169 +
  1.3170 +  /**
  1.3171 +   * Report STK Service is running.
  1.3172 +   */
  1.3173 +  reportStkServiceIsRunning: function() {
  1.3174 +    this.context.Buf.simpleRequest(REQUEST_REPORT_STK_SERVICE_IS_RUNNING);
  1.3175 +  },
  1.3176 +
  1.3177 +  /**
  1.3178 +   * Process ICC status.
  1.3179 +   */
  1.3180 +  _processICCStatus: function(iccStatus) {
  1.3181 +    // If |_waitingRadioTech| is true, we should not get app information because
  1.3182 +    // the |_isCdma| flag is not ready yet. Otherwise we may use wrong index to
  1.3183 +    // get app information, especially for the case that icc card has both cdma
  1.3184 +    // and gsm subscription.
  1.3185 +    if (this._waitingRadioTech) {
  1.3186 +      return;
  1.3187 +    }
  1.3188 +
  1.3189 +    this.iccStatus = iccStatus;
  1.3190 +    let newCardState;
  1.3191 +    let index = this._isCdma ? iccStatus.cdmaSubscriptionAppIndex :
  1.3192 +                               iccStatus.gsmUmtsSubscriptionAppIndex;
  1.3193 +    let app = iccStatus.apps[index];
  1.3194 +
  1.3195 +    // When |iccStatus.cardState| is not CARD_STATE_PRESENT or have incorrect
  1.3196 +    // app information, we can not get iccId. So treat ICC as undetected.
  1.3197 +    if (iccStatus.cardState !== CARD_STATE_PRESENT || !app) {
  1.3198 +      if (this.cardState !== GECKO_CARDSTATE_UNDETECTED) {
  1.3199 +        this.operator = null;
  1.3200 +        // We should send |cardstatechange| before |iccinfochange|, otherwise we
  1.3201 +        // may lost cardstatechange event when icc card becomes undetected.
  1.3202 +        this.cardState = GECKO_CARDSTATE_UNDETECTED;
  1.3203 +        this.sendChromeMessage({rilMessageType: "cardstatechange",
  1.3204 +                                cardState: this.cardState});
  1.3205 +
  1.3206 +        this.iccInfo = {iccType: null};
  1.3207 +        this.context.ICCUtilsHelper.handleICCInfoChange();
  1.3208 +      }
  1.3209 +      return;
  1.3210 +    }
  1.3211 +
  1.3212 +    let ICCRecordHelper = this.context.ICCRecordHelper;
  1.3213 +    // fetchICCRecords will need to read aid, so read aid here.
  1.3214 +    this.aid = app.aid;
  1.3215 +    this.appType = app.app_type;
  1.3216 +    this.iccInfo.iccType = GECKO_CARD_TYPE[this.appType];
  1.3217 +    // Try to get iccId only when cardState left GECKO_CARDSTATE_UNDETECTED.
  1.3218 +    if (iccStatus.cardState === CARD_STATE_PRESENT &&
  1.3219 +        (this.cardState === GECKO_CARDSTATE_UNINITIALIZED ||
  1.3220 +         this.cardState === GECKO_CARDSTATE_UNDETECTED)) {
  1.3221 +      ICCRecordHelper.readICCID();
  1.3222 +    }
  1.3223 +
  1.3224 +    switch (app.app_state) {
  1.3225 +      case CARD_APPSTATE_ILLEGAL:
  1.3226 +        newCardState = GECKO_CARDSTATE_ILLEGAL;
  1.3227 +        break;
  1.3228 +      case CARD_APPSTATE_PIN:
  1.3229 +        newCardState = GECKO_CARDSTATE_PIN_REQUIRED;
  1.3230 +        break;
  1.3231 +      case CARD_APPSTATE_PUK:
  1.3232 +        newCardState = GECKO_CARDSTATE_PUK_REQUIRED;
  1.3233 +        break;
  1.3234 +      case CARD_APPSTATE_SUBSCRIPTION_PERSO:
  1.3235 +        newCardState = PERSONSUBSTATE[app.perso_substate];
  1.3236 +        break;
  1.3237 +      case CARD_APPSTATE_READY:
  1.3238 +        newCardState = GECKO_CARDSTATE_READY;
  1.3239 +        break;
  1.3240 +      case CARD_APPSTATE_UNKNOWN:
  1.3241 +      case CARD_APPSTATE_DETECTED:
  1.3242 +        // Fall through.
  1.3243 +      default:
  1.3244 +        newCardState = GECKO_CARDSTATE_UNKNOWN;
  1.3245 +    }
  1.3246 +
  1.3247 +    let pin1State = app.pin1_replaced ? iccStatus.universalPINState :
  1.3248 +                                        app.pin1;
  1.3249 +    if (pin1State === CARD_PINSTATE_ENABLED_PERM_BLOCKED) {
  1.3250 +      newCardState = GECKO_CARDSTATE_PERMANENT_BLOCKED;
  1.3251 +    }
  1.3252 +
  1.3253 +    if (this.cardState == newCardState) {
  1.3254 +      return;
  1.3255 +    }
  1.3256 +
  1.3257 +    // This was moved down from CARD_APPSTATE_READY
  1.3258 +    this.requestNetworkInfo();
  1.3259 +    if (newCardState == GECKO_CARDSTATE_READY) {
  1.3260 +      // For type SIM, we need to check EF_phase first.
  1.3261 +      // Other types of ICC we can send Terminal_Profile immediately.
  1.3262 +      if (this.appType == CARD_APPTYPE_SIM) {
  1.3263 +        this.context.SimRecordHelper.readSimPhase();
  1.3264 +      } else if (RILQUIRKS_SEND_STK_PROFILE_DOWNLOAD) {
  1.3265 +        this.sendStkTerminalProfile(STK_SUPPORTED_TERMINAL_PROFILE);
  1.3266 +      }
  1.3267 +
  1.3268 +      ICCRecordHelper.fetchICCRecords();
  1.3269 +    }
  1.3270 +
  1.3271 +    this.cardState = newCardState;
  1.3272 +    this.sendChromeMessage({rilMessageType: "cardstatechange",
  1.3273 +                            cardState: this.cardState});
  1.3274 +  },
  1.3275 +
  1.3276 +   /**
  1.3277 +   * Helper for processing responses of functions such as enterICC* and changeICC*.
  1.3278 +   */
  1.3279 +  _processEnterAndChangeICCResponses: function(length, options) {
  1.3280 +    options.success = (options.rilRequestError === 0);
  1.3281 +    if (!options.success) {
  1.3282 +      options.errorMsg = RIL_ERROR_TO_GECKO_ERROR[options.rilRequestError];
  1.3283 +    }
  1.3284 +    options.retryCount = length ? this.context.Buf.readInt32List()[0] : -1;
  1.3285 +    if (options.rilMessageType != "sendMMI") {
  1.3286 +      this.sendChromeMessage(options);
  1.3287 +      return;
  1.3288 +    }
  1.3289 +
  1.3290 +    let mmiServiceCode = options.mmiServiceCode;
  1.3291 +
  1.3292 +    if (options.success) {
  1.3293 +      switch (mmiServiceCode) {
  1.3294 +        case MMI_KS_SC_PIN:
  1.3295 +          options.statusMessage = MMI_SM_KS_PIN_CHANGED;
  1.3296 +          break;
  1.3297 +        case MMI_KS_SC_PIN2:
  1.3298 +          options.statusMessage = MMI_SM_KS_PIN2_CHANGED;
  1.3299 +          break;
  1.3300 +        case MMI_KS_SC_PUK:
  1.3301 +          options.statusMessage = MMI_SM_KS_PIN_UNBLOCKED;
  1.3302 +          break;
  1.3303 +        case MMI_KS_SC_PUK2:
  1.3304 +          options.statusMessage = MMI_SM_KS_PIN2_UNBLOCKED;
  1.3305 +          break;
  1.3306 +      }
  1.3307 +    } else {
  1.3308 +      if (options.retryCount <= 0) {
  1.3309 +        if (mmiServiceCode === MMI_KS_SC_PUK) {
  1.3310 +          options.errorMsg = MMI_ERROR_KS_SIM_BLOCKED;
  1.3311 +        } else if (mmiServiceCode === MMI_KS_SC_PIN) {
  1.3312 +          options.errorMsg = MMI_ERROR_KS_NEEDS_PUK;
  1.3313 +        }
  1.3314 +      } else {
  1.3315 +        if (mmiServiceCode === MMI_KS_SC_PIN ||
  1.3316 +            mmiServiceCode === MMI_KS_SC_PIN2) {
  1.3317 +          options.errorMsg = MMI_ERROR_KS_BAD_PIN;
  1.3318 +        } else if (mmiServiceCode === MMI_KS_SC_PUK ||
  1.3319 +                   mmiServiceCode === MMI_KS_SC_PUK2) {
  1.3320 +          options.errorMsg = MMI_ERROR_KS_BAD_PUK;
  1.3321 +        }
  1.3322 +        if (options.retryCount !== undefined) {
  1.3323 +          options.additionalInformation = options.retryCount;
  1.3324 +        }
  1.3325 +      }
  1.3326 +    }
  1.3327 +
  1.3328 +    this.sendChromeMessage(options);
  1.3329 +  },
  1.3330 +
  1.3331 +  // We combine all of the NETWORK_INFO_MESSAGE_TYPES into one "networkinfochange"
  1.3332 +  // message to the RadioInterfaceLayer, so we can avoid sending multiple
  1.3333 +  // VoiceInfoChanged events for both operator / voice_data_registration
  1.3334 +  //
  1.3335 +  // State management here is a little tricky. We need to know both:
  1.3336 +  // 1. Whether or not a response was received for each of the
  1.3337 +  //    NETWORK_INFO_MESSAGE_TYPES
  1.3338 +  // 2. The outbound message that corresponds with that response -- but this
  1.3339 +  //    only happens when internal state changes (i.e. it isn't guaranteed)
  1.3340 +  //
  1.3341 +  // To collect this state, each message response function first calls
  1.3342 +  // _receivedNetworkInfo, to mark the response as received. When the
  1.3343 +  // final response is received, a call to _sendPendingNetworkInfo is placed
  1.3344 +  // on the next tick of the worker thread.
  1.3345 +  //
  1.3346 +  // Since the original call to _receivedNetworkInfo happens at the top
  1.3347 +  // of the response handler, this gives the final handler a chance to
  1.3348 +  // queue up it's "changed" message by calling _sendNetworkInfoMessage if/when
  1.3349 +  // the internal state has actually changed.
  1.3350 +  _sendNetworkInfoMessage: function(type, message) {
  1.3351 +    if (!this._processingNetworkInfo) {
  1.3352 +      // We only combine these messages in the case of the combined request
  1.3353 +      // in requestNetworkInfo()
  1.3354 +      this.sendChromeMessage(message);
  1.3355 +      return;
  1.3356 +    }
  1.3357 +
  1.3358 +    if (DEBUG) {
  1.3359 +      this.context.debug("Queuing " + type + " network info message: " +
  1.3360 +                         JSON.stringify(message));
  1.3361 +    }
  1.3362 +    this._pendingNetworkInfo[type] = message;
  1.3363 +  },
  1.3364 +
  1.3365 +  _receivedNetworkInfo: function(type) {
  1.3366 +    if (DEBUG) this.context.debug("Received " + type + " network info.");
  1.3367 +    if (!this._processingNetworkInfo) {
  1.3368 +      return;
  1.3369 +    }
  1.3370 +
  1.3371 +    let pending = this._pendingNetworkInfo;
  1.3372 +
  1.3373 +    // We still need to track states for events that aren't fired.
  1.3374 +    if (!(type in pending)) {
  1.3375 +      pending[type] = this.pendingNetworkType;
  1.3376 +    }
  1.3377 +
  1.3378 +    // Pending network info is ready to be sent when no more messages
  1.3379 +    // are waiting for responses, but the combined payload hasn't been sent.
  1.3380 +    for (let i = 0; i < NETWORK_INFO_MESSAGE_TYPES.length; i++) {
  1.3381 +      let msgType = NETWORK_INFO_MESSAGE_TYPES[i];
  1.3382 +      if (!(msgType in pending)) {
  1.3383 +        if (DEBUG) {
  1.3384 +          this.context.debug("Still missing some more network info, not " +
  1.3385 +                             "notifying main thread.");
  1.3386 +        }
  1.3387 +        return;
  1.3388 +      }
  1.3389 +    }
  1.3390 +
  1.3391 +    // Do a pass to clean up the processed messages that didn't create
  1.3392 +    // a response message, so we don't have unused keys in the outbound
  1.3393 +    // networkinfochanged message.
  1.3394 +    for (let key in pending) {
  1.3395 +      if (pending[key] == this.pendingNetworkType) {
  1.3396 +        delete pending[key];
  1.3397 +      }
  1.3398 +    }
  1.3399 +
  1.3400 +    if (DEBUG) {
  1.3401 +      this.context.debug("All pending network info has been received: " +
  1.3402 +                         JSON.stringify(pending));
  1.3403 +    }
  1.3404 +
  1.3405 +    // Send the message on the next tick of the worker's loop, so we give the
  1.3406 +    // last message a chance to call _sendNetworkInfoMessage first.
  1.3407 +    setTimeout(this._sendPendingNetworkInfo.bind(this), 0);
  1.3408 +  },
  1.3409 +
  1.3410 +  _sendPendingNetworkInfo: function() {
  1.3411 +    this.sendChromeMessage(this._pendingNetworkInfo);
  1.3412 +
  1.3413 +    this._processingNetworkInfo = false;
  1.3414 +    for (let i = 0; i < NETWORK_INFO_MESSAGE_TYPES.length; i++) {
  1.3415 +      delete this._pendingNetworkInfo[NETWORK_INFO_MESSAGE_TYPES[i]];
  1.3416 +    }
  1.3417 +
  1.3418 +    if (this._needRepollNetworkInfo) {
  1.3419 +      this._needRepollNetworkInfo = false;
  1.3420 +      this.requestNetworkInfo();
  1.3421 +    }
  1.3422 +  },
  1.3423 +
  1.3424 +  /**
  1.3425 +   * Normalize the signal strength in dBm to the signal level from 0 to 100.
  1.3426 +   *
  1.3427 +   * @param signal
  1.3428 +   *        The signal strength in dBm to normalize.
  1.3429 +   * @param min
  1.3430 +   *        The signal strength in dBm maps to level 0.
  1.3431 +   * @param max
  1.3432 +   *        The signal strength in dBm maps to level 100.
  1.3433 +   *
  1.3434 +   * @return level
  1.3435 +   *         The signal level from 0 to 100.
  1.3436 +   */
  1.3437 +  _processSignalLevel: function(signal, min, max) {
  1.3438 +    if (signal <= min) {
  1.3439 +      return 0;
  1.3440 +    }
  1.3441 +
  1.3442 +    if (signal >= max) {
  1.3443 +      return 100;
  1.3444 +    }
  1.3445 +
  1.3446 +    return Math.floor((signal - min) * 100 / (max - min));
  1.3447 +  },
  1.3448 +
  1.3449 +  /**
  1.3450 +   * Process LTE signal strength to the signal info object.
  1.3451 +   *
  1.3452 +   * @param signal
  1.3453 +   *        The signal object reported from RIL/modem.
  1.3454 +   *
  1.3455 +   * @return The object of signal strength info.
  1.3456 +   *         Or null if invalid signal input.
  1.3457 +   */
  1.3458 +  _processLteSignal: function(signal) {
  1.3459 +    // Valid values are 0-63 as defined in TS 27.007 clause 8.69.
  1.3460 +    if (signal.lteSignalStrength === undefined ||
  1.3461 +        signal.lteSignalStrength < 0 ||
  1.3462 +        signal.lteSignalStrength > 63) {
  1.3463 +      return null;
  1.3464 +    }
  1.3465 +
  1.3466 +    let info = {
  1.3467 +      voice: {
  1.3468 +        signalStrength:    null,
  1.3469 +        relSignalStrength: null
  1.3470 +      },
  1.3471 +      data: {
  1.3472 +        signalStrength:    null,
  1.3473 +        relSignalStrength: null
  1.3474 +      }
  1.3475 +    };
  1.3476 +
  1.3477 +    // TODO: Bug 982013: reconsider signalStrength/relSignalStrength APIs for
  1.3478 +    //       GSM/CDMA/LTE, and take rsrp/rssnr into account for LTE case then.
  1.3479 +    let signalStrength = -111 + signal.lteSignalStrength;
  1.3480 +    info.voice.signalStrength = info.data.signalStrength = signalStrength;
  1.3481 +    // 0 and 12 are referred to AOSP's implementation. These values are not
  1.3482 +    // constants and can be customized based on different requirements.
  1.3483 +    let signalLevel = this._processSignalLevel(signal.lteSignalStrength, 0, 12);
  1.3484 +    info.voice.relSignalStrength = info.data.relSignalStrength = signalLevel;
  1.3485 +
  1.3486 +    return info;
  1.3487 +  },
  1.3488 +
  1.3489 +  _processSignalStrength: function(signal) {
  1.3490 +    let info = {
  1.3491 +      voice: {
  1.3492 +        signalStrength:    null,
  1.3493 +        relSignalStrength: null
  1.3494 +      },
  1.3495 +      data: {
  1.3496 +        signalStrength:    null,
  1.3497 +        relSignalStrength: null
  1.3498 +      }
  1.3499 +    };
  1.3500 +
  1.3501 +    // During startup, |radioTech| is not yet defined, so we need to
  1.3502 +    // check it separately.
  1.3503 +    if (("radioTech" in this.voiceRegistrationState) &&
  1.3504 +        !this._isGsmTechGroup(this.voiceRegistrationState.radioTech)) {
  1.3505 +      // CDMA RSSI.
  1.3506 +      // Valid values are positive integers. This value is the actual RSSI value
  1.3507 +      // multiplied by -1. Example: If the actual RSSI is -75, then this
  1.3508 +      // response value will be 75.
  1.3509 +      if (signal.cdmaDBM && signal.cdmaDBM > 0) {
  1.3510 +        let signalStrength = -1 * signal.cdmaDBM;
  1.3511 +        info.voice.signalStrength = signalStrength;
  1.3512 +
  1.3513 +        // -105 and -70 are referred to AOSP's implementation. These values are
  1.3514 +        // not constants and can be customized based on different requirement.
  1.3515 +        let signalLevel = this._processSignalLevel(signalStrength, -105, -70);
  1.3516 +        info.voice.relSignalStrength = signalLevel;
  1.3517 +      }
  1.3518 +
  1.3519 +      // EVDO RSSI.
  1.3520 +      // Valid values are positive integers. This value is the actual RSSI value
  1.3521 +      // multiplied by -1. Example: If the actual RSSI is -75, then this
  1.3522 +      // response value will be 75.
  1.3523 +      if (signal.evdoDBM && signal.evdoDBM > 0) {
  1.3524 +        let signalStrength = -1 * signal.evdoDBM;
  1.3525 +        info.data.signalStrength = signalStrength;
  1.3526 +
  1.3527 +        // -105 and -70 are referred to AOSP's implementation. These values are
  1.3528 +        // not constants and can be customized based on different requirement.
  1.3529 +        let signalLevel = this._processSignalLevel(signalStrength, -105, -70);
  1.3530 +        info.data.relSignalStrength = signalLevel;
  1.3531 +      }
  1.3532 +    } else {
  1.3533 +      // Check LTE level first, and check GSM/UMTS level next if LTE one is not
  1.3534 +      // valid.
  1.3535 +      let lteInfo = this._processLteSignal(signal);
  1.3536 +      if (lteInfo) {
  1.3537 +        info = lteInfo;
  1.3538 +      } else {
  1.3539 +        // GSM signal strength.
  1.3540 +        // Valid values are 0-31 as defined in TS 27.007 8.5.
  1.3541 +        // 0     : -113 dBm or less
  1.3542 +        // 1     : -111 dBm
  1.3543 +        // 2...30: -109...-53 dBm
  1.3544 +        // 31    : -51 dBm
  1.3545 +        if (signal.gsmSignalStrength &&
  1.3546 +            signal.gsmSignalStrength >= 0 &&
  1.3547 +            signal.gsmSignalStrength <= 31) {
  1.3548 +          let signalStrength = -113 + 2 * signal.gsmSignalStrength;
  1.3549 +          info.voice.signalStrength = info.data.signalStrength = signalStrength;
  1.3550 +
  1.3551 +          // -115 and -85 are referred to AOSP's implementation. These values are
  1.3552 +          // not constants and can be customized based on different requirement.
  1.3553 +          let signalLevel = this._processSignalLevel(signalStrength, -110, -85);
  1.3554 +          info.voice.relSignalStrength = info.data.relSignalStrength = signalLevel;
  1.3555 +        }
  1.3556 +      }
  1.3557 +    }
  1.3558 +
  1.3559 +    info.rilMessageType = "signalstrengthchange";
  1.3560 +    this._sendNetworkInfoMessage(NETWORK_INFO_SIGNAL, info);
  1.3561 +
  1.3562 +    if (this.cachedDialRequest && info.voice.signalStrength) {
  1.3563 +      // Radio is ready for making the cached emergency call.
  1.3564 +      this.cachedDialRequest.callback();
  1.3565 +      this.cachedDialRequest = null;
  1.3566 +    }
  1.3567 +  },
  1.3568 +
  1.3569 +  /**
  1.3570 +   * Process the network registration flags.
  1.3571 +   *
  1.3572 +   * @return true if the state changed, false otherwise.
  1.3573 +   */
  1.3574 +  _processCREG: function(curState, newState) {
  1.3575 +    let changed = false;
  1.3576 +
  1.3577 +    let regState = this.parseInt(newState[0], NETWORK_CREG_STATE_UNKNOWN);
  1.3578 +    if (curState.regState === undefined || curState.regState !== regState) {
  1.3579 +      changed = true;
  1.3580 +      curState.regState = regState;
  1.3581 +
  1.3582 +      curState.state = NETWORK_CREG_TO_GECKO_MOBILE_CONNECTION_STATE[regState];
  1.3583 +      curState.connected = regState == NETWORK_CREG_STATE_REGISTERED_HOME ||
  1.3584 +                           regState == NETWORK_CREG_STATE_REGISTERED_ROAMING;
  1.3585 +      curState.roaming = regState == NETWORK_CREG_STATE_REGISTERED_ROAMING;
  1.3586 +      curState.emergencyCallsOnly = !curState.connected;
  1.3587 +    }
  1.3588 +
  1.3589 +    if (!curState.cell) {
  1.3590 +      curState.cell = {};
  1.3591 +    }
  1.3592 +
  1.3593 +    // From TS 23.003, 0000 and 0xfffe are indicated that no valid LAI exists
  1.3594 +    // in MS. So we still need to report the '0000' as well.
  1.3595 +    let lac = this.parseInt(newState[1], -1, 16);
  1.3596 +    if (curState.cell.gsmLocationAreaCode === undefined ||
  1.3597 +        curState.cell.gsmLocationAreaCode !== lac) {
  1.3598 +      curState.cell.gsmLocationAreaCode = lac;
  1.3599 +      changed = true;
  1.3600 +    }
  1.3601 +
  1.3602 +    let cid = this.parseInt(newState[2], -1, 16);
  1.3603 +    if (curState.cell.gsmCellId === undefined ||
  1.3604 +        curState.cell.gsmCellId !== cid) {
  1.3605 +      curState.cell.gsmCellId = cid;
  1.3606 +      changed = true;
  1.3607 +    }
  1.3608 +
  1.3609 +    let radioTech = (newState[3] === undefined ?
  1.3610 +                     NETWORK_CREG_TECH_UNKNOWN :
  1.3611 +                     this.parseInt(newState[3], NETWORK_CREG_TECH_UNKNOWN));
  1.3612 +    if (curState.radioTech === undefined || curState.radioTech !== radioTech) {
  1.3613 +      changed = true;
  1.3614 +      curState.radioTech = radioTech;
  1.3615 +      curState.type = GECKO_RADIO_TECH[radioTech] || null;
  1.3616 +    }
  1.3617 +    return changed;
  1.3618 +  },
  1.3619 +
  1.3620 +  _processVoiceRegistrationState: function(state) {
  1.3621 +    let rs = this.voiceRegistrationState;
  1.3622 +    let stateChanged = this._processCREG(rs, state);
  1.3623 +    if (stateChanged && rs.connected) {
  1.3624 +      this.getSmscAddress();
  1.3625 +    }
  1.3626 +
  1.3627 +    let cell = rs.cell;
  1.3628 +    if (this._isCdma) {
  1.3629 +      // Some variables below are not used. Comment them instead of removing to
  1.3630 +      // keep the information about state[x].
  1.3631 +      let cdmaBaseStationId = this.parseInt(state[4], -1);
  1.3632 +      let cdmaBaseStationLatitude = this.parseInt(state[5], -2147483648);
  1.3633 +      let cdmaBaseStationLongitude = this.parseInt(state[6], -2147483648);
  1.3634 +      // let cssIndicator = this.parseInt(state[7]);
  1.3635 +      let cdmaSystemId = this.parseInt(state[8], -1);
  1.3636 +      let cdmaNetworkId = this.parseInt(state[9], -1);
  1.3637 +      // let roamingIndicator = this.parseInt(state[10]);
  1.3638 +      // let systemIsInPRL = this.parseInt(state[11]);
  1.3639 +      // let defaultRoamingIndicator = this.parseInt(state[12]);
  1.3640 +      // let reasonForDenial = this.parseInt(state[13]);
  1.3641 +
  1.3642 +      if (cell.cdmaBaseStationId !== cdmaBaseStationId ||
  1.3643 +          cell.cdmaBaseStationLatitude !== cdmaBaseStationLatitude ||
  1.3644 +          cell.cdmaBaseStationLongitude !== cdmaBaseStationLongitude ||
  1.3645 +          cell.cdmaSystemId !== cdmaSystemId ||
  1.3646 +          cell.cdmaNetworkId !== cdmaNetworkId) {
  1.3647 +        stateChanged = true;
  1.3648 +        cell.cdmaBaseStationId = cdmaBaseStationId;
  1.3649 +        cell.cdmaBaseStationLatitude = cdmaBaseStationLatitude;
  1.3650 +        cell.cdmaBaseStationLongitude = cdmaBaseStationLongitude;
  1.3651 +        cell.cdmaSystemId = cdmaSystemId;
  1.3652 +        cell.cdmaNetworkId = cdmaNetworkId;
  1.3653 +      }
  1.3654 +    }
  1.3655 +
  1.3656 +    if (stateChanged) {
  1.3657 +      rs.rilMessageType = "voiceregistrationstatechange";
  1.3658 +      this._sendNetworkInfoMessage(NETWORK_INFO_VOICE_REGISTRATION_STATE, rs);
  1.3659 +    }
  1.3660 +  },
  1.3661 +
  1.3662 +  _processDataRegistrationState: function(state) {
  1.3663 +    let rs = this.dataRegistrationState;
  1.3664 +    let stateChanged = this._processCREG(rs, state);
  1.3665 +    if (stateChanged) {
  1.3666 +      rs.rilMessageType = "dataregistrationstatechange";
  1.3667 +      this._sendNetworkInfoMessage(NETWORK_INFO_DATA_REGISTRATION_STATE, rs);
  1.3668 +    }
  1.3669 +  },
  1.3670 +
  1.3671 +  _processOperator: function(operatorData) {
  1.3672 +    if (operatorData.length < 3) {
  1.3673 +      if (DEBUG) {
  1.3674 +        this.context.debug("Expected at least 3 strings for operator.");
  1.3675 +      }
  1.3676 +    }
  1.3677 +
  1.3678 +    if (!this.operator) {
  1.3679 +      this.operator = {
  1.3680 +        rilMessageType: "operatorchange",
  1.3681 +        longName: null,
  1.3682 +        shortName: null
  1.3683 +      };
  1.3684 +    }
  1.3685 +
  1.3686 +    let [longName, shortName, networkTuple] = operatorData;
  1.3687 +    let thisTuple = (this.operator.mcc || "") + (this.operator.mnc || "");
  1.3688 +
  1.3689 +    if (this.operator.longName !== longName ||
  1.3690 +        this.operator.shortName !== shortName ||
  1.3691 +        thisTuple !== networkTuple) {
  1.3692 +
  1.3693 +      this.operator.mcc = null;
  1.3694 +      this.operator.mnc = null;
  1.3695 +
  1.3696 +      if (networkTuple) {
  1.3697 +        try {
  1.3698 +          this._processNetworkTuple(networkTuple, this.operator);
  1.3699 +        } catch (e) {
  1.3700 +          if (DEBUG) this.context.debug("Error processing operator tuple: " + e);
  1.3701 +        }
  1.3702 +      } else {
  1.3703 +        // According to ril.h, the operator fields will be NULL when the operator
  1.3704 +        // is not currently registered. We can avoid trying to parse the numeric
  1.3705 +        // tuple in that case.
  1.3706 +        if (DEBUG) {
  1.3707 +          this.context.debug("Operator is currently unregistered");
  1.3708 +        }
  1.3709 +      }
  1.3710 +
  1.3711 +      let ICCUtilsHelper = this.context.ICCUtilsHelper;
  1.3712 +      let networkName;
  1.3713 +      // We won't get network name using PNN and OPL if voice registration isn't ready
  1.3714 +      if (this.voiceRegistrationState.cell &&
  1.3715 +          this.voiceRegistrationState.cell.gsmLocationAreaCode != -1) {
  1.3716 +        networkName = ICCUtilsHelper.getNetworkNameFromICC(
  1.3717 +          this.operator.mcc,
  1.3718 +          this.operator.mnc,
  1.3719 +          this.voiceRegistrationState.cell.gsmLocationAreaCode);
  1.3720 +      }
  1.3721 +
  1.3722 +      if (networkName) {
  1.3723 +        if (DEBUG) {
  1.3724 +          this.context.debug("Operator names will be overriden: " +
  1.3725 +                             "longName = " + networkName.fullName + ", " +
  1.3726 +                             "shortName = " + networkName.shortName);
  1.3727 +        }
  1.3728 +
  1.3729 +        this.operator.longName = networkName.fullName;
  1.3730 +        this.operator.shortName = networkName.shortName;
  1.3731 +      } else {
  1.3732 +        this.operator.longName = longName;
  1.3733 +        this.operator.shortName = shortName;
  1.3734 +      }
  1.3735 +
  1.3736 +      if (ICCUtilsHelper.updateDisplayCondition()) {
  1.3737 +        ICCUtilsHelper.handleICCInfoChange();
  1.3738 +      }
  1.3739 +      this._sendNetworkInfoMessage(NETWORK_INFO_OPERATOR, this.operator);
  1.3740 +    }
  1.3741 +  },
  1.3742 +
  1.3743 +  /**
  1.3744 +   * Helpers for processing call state and handle the active call.
  1.3745 +   */
  1.3746 +  _processCalls: function(newCalls) {
  1.3747 +    let conferenceChanged = false;
  1.3748 +    let clearConferenceRequest = false;
  1.3749 +    let pendingOutgoingCall = null;
  1.3750 +
  1.3751 +    // Go through the calls we currently have on file and see if any of them
  1.3752 +    // changed state. Remove them from the newCalls map as we deal with them
  1.3753 +    // so that only new calls remain in the map after we're done.
  1.3754 +    for each (let currentCall in this.currentCalls) {
  1.3755 +      if (currentCall.callIndex == OUTGOING_PLACEHOLDER_CALL_INDEX) {
  1.3756 +        pendingOutgoingCall = currentCall;
  1.3757 +        continue;
  1.3758 +      }
  1.3759 +
  1.3760 +      let newCall;
  1.3761 +      if (newCalls) {
  1.3762 +        newCall = newCalls[currentCall.callIndex];
  1.3763 +        delete newCalls[currentCall.callIndex];
  1.3764 +      }
  1.3765 +
  1.3766 +      // Call is no longer reported by the radio. Remove from our map and send
  1.3767 +      // disconnected state change.
  1.3768 +      if (!newCall) {
  1.3769 +        if (this.currentConference.participants[currentCall.callIndex]) {
  1.3770 +          conferenceChanged = true;
  1.3771 +        }
  1.3772 +        this._removeVoiceCall(currentCall,
  1.3773 +                              currentCall.hangUpLocal ?
  1.3774 +                                GECKO_CALL_ERROR_NORMAL_CALL_CLEARING : null);
  1.3775 +        continue;
  1.3776 +      }
  1.3777 +
  1.3778 +      // Call is still valid.
  1.3779 +      if (newCall.state == currentCall.state &&
  1.3780 +          newCall.isMpty == currentCall.isMpty) {
  1.3781 +        continue;
  1.3782 +      }
  1.3783 +
  1.3784 +      // State has changed.
  1.3785 +      if (newCall.state == CALL_STATE_INCOMING &&
  1.3786 +          currentCall.state == CALL_STATE_WAITING) {
  1.3787 +        // Update the call internally but we don't notify chrome since these two
  1.3788 +        // states are viewed as the same one there.
  1.3789 +        currentCall.state = newCall.state;
  1.3790 +        continue;
  1.3791 +      }
  1.3792 +
  1.3793 +      if (!currentCall.started && newCall.state == CALL_STATE_ACTIVE) {
  1.3794 +        currentCall.started = new Date().getTime();
  1.3795 +      }
  1.3796 +
  1.3797 +      if (currentCall.isMpty == newCall.isMpty &&
  1.3798 +          newCall.state != currentCall.state) {
  1.3799 +        currentCall.state = newCall.state;
  1.3800 +        if (currentCall.isConference) {
  1.3801 +          conferenceChanged = true;
  1.3802 +        }
  1.3803 +        this._handleChangedCallState(currentCall);
  1.3804 +        continue;
  1.3805 +      }
  1.3806 +
  1.3807 +      // '.isMpty' becomes false when the conference call is put on hold.
  1.3808 +      // We need to introduce additional 'isConference' to correctly record the
  1.3809 +      // real conference status
  1.3810 +
  1.3811 +      // Update a possible conference participant when .isMpty changes.
  1.3812 +      if (!currentCall.isMpty && newCall.isMpty) {
  1.3813 +        if (this._hasConferenceRequest) {
  1.3814 +          conferenceChanged = true;
  1.3815 +          clearConferenceRequest = true;
  1.3816 +          currentCall.state = newCall.state;
  1.3817 +          currentCall.isMpty = newCall.isMpty;
  1.3818 +          currentCall.isConference = true;
  1.3819 +          this.currentConference.participants[currentCall.callIndex] = currentCall;
  1.3820 +          this._handleChangedCallState(currentCall);
  1.3821 +        } else if (currentCall.isConference) {
  1.3822 +          // The case happens when resuming a held conference call.
  1.3823 +          conferenceChanged = true;
  1.3824 +          currentCall.state = newCall.state;
  1.3825 +          currentCall.isMpty = newCall.isMpty;
  1.3826 +          this.currentConference.participants[currentCall.callIndex] = currentCall;
  1.3827 +          this._handleChangedCallState(currentCall);
  1.3828 +        } else {
  1.3829 +          // Weird. This sometimes happens when we switch two calls, but it is
  1.3830 +          // not a conference call.
  1.3831 +          currentCall.state = newCall.state;
  1.3832 +          this._handleChangedCallState(currentCall);
  1.3833 +        }
  1.3834 +      } else if (currentCall.isMpty && !newCall.isMpty) {
  1.3835 +        if (!this.currentConference.participants[newCall.callIndex]) {
  1.3836 +          continue;
  1.3837 +        }
  1.3838 +
  1.3839 +        // '.isMpty' of a conference participant is set to false by rild when
  1.3840 +        // the conference call is put on hold. We don't actually know if the call
  1.3841 +        // still attends the conference until updating all calls finishes. We
  1.3842 +        // cache it for further determination.
  1.3843 +        if (newCall.state != CALL_STATE_HOLDING) {
  1.3844 +          delete this.currentConference.participants[newCall.callIndex];
  1.3845 +          currentCall.state = newCall.state;
  1.3846 +          currentCall.isMpty = newCall.isMpty;
  1.3847 +          currentCall.isConference = false;
  1.3848 +          conferenceChanged = true;
  1.3849 +          this._handleChangedCallState(currentCall);
  1.3850 +          continue;
  1.3851 +        }
  1.3852 +
  1.3853 +        if (!this.currentConference.cache) {
  1.3854 +          this.currentConference.cache = {};
  1.3855 +        }
  1.3856 +        this.currentConference.cache[currentCall.callIndex] = newCall;
  1.3857 +        currentCall.state = newCall.state;
  1.3858 +        currentCall.isMpty = newCall.isMpty;
  1.3859 +        conferenceChanged = true;
  1.3860 +      }
  1.3861 +    }
  1.3862 +
  1.3863 +    if (pendingOutgoingCall) {
  1.3864 +      if (!newCalls || Object.keys(newCalls).length === 0) {
  1.3865 +        // We don't get a successful call for pendingOutgoingCall.
  1.3866 +        this._removePendingOutgoingCall(GECKO_CALL_ERROR_UNSPECIFIED);
  1.3867 +      } else {
  1.3868 +        // Only remove it from currentCalls map. Will use the new call to
  1.3869 +        // replace the placeholder.
  1.3870 +        delete this.currentCalls[OUTGOING_PLACEHOLDER_CALL_INDEX];
  1.3871 +      }
  1.3872 +    }
  1.3873 +
  1.3874 +    // Go through any remaining calls that are new to us.
  1.3875 +    for each (let newCall in newCalls) {
  1.3876 +      if (newCall.isVoice) {
  1.3877 +        if (newCall.isMpty) {
  1.3878 +          conferenceChanged = true;
  1.3879 +        }
  1.3880 +        if (!pendingOutgoingCall &&
  1.3881 +            (newCall.state === CALL_STATE_DIALING ||
  1.3882 +             newCall.state === CALL_STATE_ALERTING)) {
  1.3883 +          // Receive a new outgoing call which is already hung up by user.
  1.3884 +          if (DEBUG) this.context.debug("Pending outgoing call is hung up by user.");
  1.3885 +          this.sendHangUpRequest(newCall.callIndex);
  1.3886 +        } else {
  1.3887 +          this._addNewVoiceCall(newCall);
  1.3888 +        }
  1.3889 +      }
  1.3890 +    }
  1.3891 +
  1.3892 +    if (clearConferenceRequest) {
  1.3893 +      this._hasConferenceRequest = false;
  1.3894 +    }
  1.3895 +    if (conferenceChanged) {
  1.3896 +      this._ensureConference();
  1.3897 +    }
  1.3898 +  },
  1.3899 +
  1.3900 +  _addNewVoiceCall: function(newCall) {
  1.3901 +    // Format international numbers appropriately.
  1.3902 +    if (newCall.number && newCall.toa == TOA_INTERNATIONAL &&
  1.3903 +        newCall.number[0] != "+") {
  1.3904 +      newCall.number = "+" + newCall.number;
  1.3905 +    }
  1.3906 +
  1.3907 +    if (newCall.state == CALL_STATE_INCOMING) {
  1.3908 +      newCall.isOutgoing = false;
  1.3909 +    } else if (newCall.state == CALL_STATE_DIALING) {
  1.3910 +      newCall.isOutgoing = true;
  1.3911 +    }
  1.3912 +
  1.3913 +    // Set flag for outgoing emergency call.
  1.3914 +    newCall.isEmergency = newCall.isOutgoing &&
  1.3915 +      this._isEmergencyNumber(newCall.number);
  1.3916 +
  1.3917 +    // Set flag for conference.
  1.3918 +    newCall.isConference = newCall.isMpty ? true : false;
  1.3919 +
  1.3920 +    // Add to our map.
  1.3921 +    if (newCall.isMpty) {
  1.3922 +      this.currentConference.participants[newCall.callIndex] = newCall;
  1.3923 +    }
  1.3924 +    this._handleChangedCallState(newCall);
  1.3925 +    this.currentCalls[newCall.callIndex] = newCall;
  1.3926 +  },
  1.3927 +
  1.3928 +  _removeVoiceCall: function(removedCall, failCause) {
  1.3929 +    if (this.currentConference.participants[removedCall.callIndex]) {
  1.3930 +      removedCall.isConference = false;
  1.3931 +      delete this.currentConference.participants[removedCall.callIndex];
  1.3932 +      delete this.currentCalls[removedCall.callIndex];
  1.3933 +      // We don't query the fail cause here as it triggers another asynchrouns
  1.3934 +      // request that leads to a problem of updating all conferece participants
  1.3935 +      // in one task.
  1.3936 +      this._handleDisconnectedCall(removedCall);
  1.3937 +    } else {
  1.3938 +      delete this.currentCalls[removedCall.callIndex];
  1.3939 +      if (failCause) {
  1.3940 +        removedCall.failCause = failCause;
  1.3941 +        this._handleDisconnectedCall(removedCall);
  1.3942 +      } else {
  1.3943 +        this.getFailCauseCode((function(call, failCause) {
  1.3944 +          call.failCause = failCause;
  1.3945 +          this._handleDisconnectedCall(call);
  1.3946 +        }).bind(this, removedCall));
  1.3947 +      }
  1.3948 +    }
  1.3949 +  },
  1.3950 +
  1.3951 +  _createPendingOutgoingCall: function(options) {
  1.3952 +    if (DEBUG) this.context.debug("Create a pending outgoing call.");
  1.3953 +    this._addNewVoiceCall({
  1.3954 +      number: options.number,
  1.3955 +      state: CALL_STATE_DIALING,
  1.3956 +      callIndex: OUTGOING_PLACEHOLDER_CALL_INDEX
  1.3957 +    });
  1.3958 +  },
  1.3959 +
  1.3960 +  _removePendingOutgoingCall: function(failCause) {
  1.3961 +    let call = this.currentCalls[OUTGOING_PLACEHOLDER_CALL_INDEX];
  1.3962 +    if (!call) {
  1.3963 +      return;
  1.3964 +    }
  1.3965 +
  1.3966 +    if (DEBUG) this.context.debug("Remove pending outgoing call.");
  1.3967 +    this._removeVoiceCall(pendingOutgoingCall, failCause);
  1.3968 +  },
  1.3969 +
  1.3970 +  _ensureConference: function() {
  1.3971 +    let oldState = this.currentConference.state;
  1.3972 +    let remaining = Object.keys(this.currentConference.participants);
  1.3973 +
  1.3974 +    if (remaining.length == 1) {
  1.3975 +      // Remove that if only does one remain in a conference call.
  1.3976 +      let call = this.currentCalls[remaining[0]];
  1.3977 +      call.isConference = false;
  1.3978 +      this._handleChangedCallState(call);
  1.3979 +      delete this.currentConference.participants[call.callIndex];
  1.3980 +    } else if (remaining.length > 1) {
  1.3981 +      for each (let call in this.currentConference.cache) {
  1.3982 +        call.isConference = true;
  1.3983 +        this.currentConference.participants[call.callIndex] = call;
  1.3984 +        this.currentCalls[call.callIndex] = call;
  1.3985 +        this._handleChangedCallState(call);
  1.3986 +      }
  1.3987 +    }
  1.3988 +    delete this.currentConference.cache;
  1.3989 +
  1.3990 +    // Update the conference call's state.
  1.3991 +    let state = CALL_STATE_UNKNOWN;
  1.3992 +    for each (let call in this.currentConference.participants) {
  1.3993 +      if (state != CALL_STATE_UNKNOWN && state != call.state) {
  1.3994 +        // Each participant should have the same state, otherwise something
  1.3995 +        // wrong happens.
  1.3996 +        state = CALL_STATE_UNKNOWN;
  1.3997 +        break;
  1.3998 +      }
  1.3999 +      state = call.state;
  1.4000 +    }
  1.4001 +    if (oldState != state) {
  1.4002 +      this.currentConference.state = state;
  1.4003 +      let message = {rilMessageType: "conferenceCallStateChanged",
  1.4004 +                     state: state};
  1.4005 +      this.sendChromeMessage(message);
  1.4006 +    }
  1.4007 +  },
  1.4008 +
  1.4009 +  _handleChangedCallState: function(changedCall) {
  1.4010 +    let message = {rilMessageType: "callStateChange",
  1.4011 +                   call: changedCall};
  1.4012 +    this.sendChromeMessage(message);
  1.4013 +  },
  1.4014 +
  1.4015 +  _handleDisconnectedCall: function(disconnectedCall) {
  1.4016 +    let message = {rilMessageType: "callDisconnected",
  1.4017 +                   call: disconnectedCall};
  1.4018 +    this.sendChromeMessage(message);
  1.4019 +  },
  1.4020 +
  1.4021 +  _sendDataCallError: function(message, errorCode) {
  1.4022 +    // Should not include token for unsolicited response.
  1.4023 +    delete message.rilMessageToken;
  1.4024 +    message.rilMessageType = "datacallerror";
  1.4025 +    if (errorCode == ERROR_GENERIC_FAILURE) {
  1.4026 +      message.errorMsg = RIL_ERROR_TO_GECKO_ERROR[errorCode];
  1.4027 +    } else {
  1.4028 +      message.errorMsg = RIL_DATACALL_FAILCAUSE_TO_GECKO_DATACALL_ERROR[errorCode];
  1.4029 +    }
  1.4030 +    this.sendChromeMessage(message);
  1.4031 +  },
  1.4032 +
  1.4033 +  /**
  1.4034 +   * @return "deactivate" if <ifname> changes or one of the currentDataCall
  1.4035 +   *         addresses is missing in updatedDataCall, or "identical" if no
  1.4036 +   *         changes found, or "changed" otherwise.
  1.4037 +   */
  1.4038 +  _compareDataCallLink: function(updatedDataCall, currentDataCall) {
  1.4039 +    // If network interface is changed, report as "deactivate".
  1.4040 +    if (updatedDataCall.ifname != currentDataCall.ifname) {
  1.4041 +      return "deactivate";
  1.4042 +    }
  1.4043 +
  1.4044 +    // If any existing address is missing, report as "deactivate".
  1.4045 +    for (let i = 0; i < currentDataCall.addresses.length; i++) {
  1.4046 +      let address = currentDataCall.addresses[i];
  1.4047 +      if (updatedDataCall.addresses.indexOf(address) < 0) {
  1.4048 +        return "deactivate";
  1.4049 +      }
  1.4050 +    }
  1.4051 +
  1.4052 +    if (currentDataCall.addresses.length != updatedDataCall.addresses.length) {
  1.4053 +      // Since now all |currentDataCall.addresses| are found in
  1.4054 +      // |updatedDataCall.addresses|, this means one or more new addresses are
  1.4055 +      // reported.
  1.4056 +      return "changed";
  1.4057 +    }
  1.4058 +
  1.4059 +    let fields = ["gateways", "dnses"];
  1.4060 +    for (let i = 0; i < fields.length; i++) {
  1.4061 +      // Compare <datacall>.<field>.
  1.4062 +      let field = fields[i];
  1.4063 +      let lhs = updatedDataCall[field], rhs = currentDataCall[field];
  1.4064 +      if (lhs.length != rhs.length) {
  1.4065 +        return "changed";
  1.4066 +      }
  1.4067 +      for (let i = 0; i < lhs.length; i++) {
  1.4068 +        if (lhs[i] != rhs[i]) {
  1.4069 +          return "changed";
  1.4070 +        }
  1.4071 +      }
  1.4072 +    }
  1.4073 +
  1.4074 +    return "identical";
  1.4075 +  },
  1.4076 +
  1.4077 +  _processDataCallList: function(datacalls, newDataCallOptions) {
  1.4078 +    // Check for possible PDP errors: We check earlier because the datacall
  1.4079 +    // can be removed if is the same as the current one.
  1.4080 +    for each (let newDataCall in datacalls) {
  1.4081 +      if (newDataCall.status != DATACALL_FAIL_NONE) {
  1.4082 +        if (newDataCallOptions) {
  1.4083 +          newDataCall.apn = newDataCallOptions.apn;
  1.4084 +        }
  1.4085 +        this._sendDataCallError(newDataCall, newDataCall.status);
  1.4086 +      }
  1.4087 +    }
  1.4088 +
  1.4089 +    for each (let currentDataCall in this.currentDataCalls) {
  1.4090 +      let updatedDataCall;
  1.4091 +      if (datacalls) {
  1.4092 +        updatedDataCall = datacalls[currentDataCall.cid];
  1.4093 +        delete datacalls[currentDataCall.cid];
  1.4094 +      }
  1.4095 +
  1.4096 +      if (!updatedDataCall) {
  1.4097 +        // If datacalls list is coming from REQUEST_SETUP_DATA_CALL response,
  1.4098 +        // we do not change state for any currentDataCalls not in datacalls list.
  1.4099 +        if (!newDataCallOptions) {
  1.4100 +          delete this.currentDataCalls[currentDataCall.cid];
  1.4101 +          currentDataCall.state = GECKO_NETWORK_STATE_DISCONNECTED;
  1.4102 +          currentDataCall.rilMessageType = "datacallstatechange";
  1.4103 +          this.sendChromeMessage(currentDataCall);
  1.4104 +        }
  1.4105 +        continue;
  1.4106 +      }
  1.4107 +
  1.4108 +      if (updatedDataCall && !updatedDataCall.ifname) {
  1.4109 +        delete this.currentDataCalls[currentDataCall.cid];
  1.4110 +        currentDataCall.state = GECKO_NETWORK_STATE_UNKNOWN;
  1.4111 +        currentDataCall.rilMessageType = "datacallstatechange";
  1.4112 +        this.sendChromeMessage(currentDataCall);
  1.4113 +        continue;
  1.4114 +      }
  1.4115 +
  1.4116 +      this._setDataCallGeckoState(updatedDataCall);
  1.4117 +      if (updatedDataCall.state != currentDataCall.state) {
  1.4118 +        if (updatedDataCall.state == GECKO_NETWORK_STATE_DISCONNECTED) {
  1.4119 +          delete this.currentDataCalls[currentDataCall.cid];
  1.4120 +        }
  1.4121 +        currentDataCall.status = updatedDataCall.status;
  1.4122 +        currentDataCall.active = updatedDataCall.active;
  1.4123 +        currentDataCall.state = updatedDataCall.state;
  1.4124 +        currentDataCall.rilMessageType = "datacallstatechange";
  1.4125 +        this.sendChromeMessage(currentDataCall);
  1.4126 +        continue;
  1.4127 +      }
  1.4128 +
  1.4129 +      // State not changed, now check links.
  1.4130 +      let result =
  1.4131 +        this._compareDataCallLink(updatedDataCall, currentDataCall);
  1.4132 +      if (result == "identical") {
  1.4133 +        if (DEBUG) this.context.debug("No changes in data call.");
  1.4134 +        continue;
  1.4135 +      }
  1.4136 +      if (result == "deactivate") {
  1.4137 +        if (DEBUG) this.context.debug("Data link changed, cleanup.");
  1.4138 +        this.deactivateDataCall(currentDataCall);
  1.4139 +        continue;
  1.4140 +      }
  1.4141 +      // Minor change, just update and notify.
  1.4142 +      if (DEBUG) {
  1.4143 +        this.context.debug("Data link minor change, just update and notify.");
  1.4144 +      }
  1.4145 +      currentDataCall.addresses = updatedDataCall.addresses.slice();
  1.4146 +      currentDataCall.dnses = updatedDataCall.dnses.slice();
  1.4147 +      currentDataCall.gateways = updatedDataCall.gateways.slice();
  1.4148 +      currentDataCall.rilMessageType = "datacallstatechange";
  1.4149 +      this.sendChromeMessage(currentDataCall);
  1.4150 +    }
  1.4151 +
  1.4152 +    for each (let newDataCall in datacalls) {
  1.4153 +      if (!newDataCall.ifname) {
  1.4154 +        continue;
  1.4155 +      }
  1.4156 +
  1.4157 +      if (!newDataCallOptions) {
  1.4158 +        if (DEBUG) {
  1.4159 +          this.context.debug("Unexpected new data call: " +
  1.4160 +                             JSON.stringify(newDataCall));
  1.4161 +        }
  1.4162 +        continue;
  1.4163 +      }
  1.4164 +
  1.4165 +      this.currentDataCalls[newDataCall.cid] = newDataCall;
  1.4166 +      this._setDataCallGeckoState(newDataCall);
  1.4167 +
  1.4168 +      newDataCall.radioTech = newDataCallOptions.radioTech;
  1.4169 +      newDataCall.apn = newDataCallOptions.apn;
  1.4170 +      newDataCall.user = newDataCallOptions.user;
  1.4171 +      newDataCall.passwd = newDataCallOptions.passwd;
  1.4172 +      newDataCall.chappap = newDataCallOptions.chappap;
  1.4173 +      newDataCall.pdptype = newDataCallOptions.pdptype;
  1.4174 +      newDataCallOptions = null;
  1.4175 +
  1.4176 +      newDataCall.rilMessageType = "datacallstatechange";
  1.4177 +      this.sendChromeMessage(newDataCall);
  1.4178 +    }
  1.4179 +  },
  1.4180 +
  1.4181 +  _setDataCallGeckoState: function(datacall) {
  1.4182 +    switch (datacall.active) {
  1.4183 +      case DATACALL_INACTIVE:
  1.4184 +        datacall.state = GECKO_NETWORK_STATE_DISCONNECTED;
  1.4185 +        break;
  1.4186 +      case DATACALL_ACTIVE_DOWN:
  1.4187 +      case DATACALL_ACTIVE_UP:
  1.4188 +        datacall.state = GECKO_NETWORK_STATE_CONNECTED;
  1.4189 +        break;
  1.4190 +    }
  1.4191 +  },
  1.4192 +
  1.4193 +  _processSuppSvcNotification: function(info) {
  1.4194 +    if (DEBUG) {
  1.4195 +      this.context.debug("handle supp svc notification: " + JSON.stringify(info));
  1.4196 +    }
  1.4197 +
  1.4198 +    if (info.notificationType !== 1) {
  1.4199 +      // We haven't supported MO intermediate result code, i.e.
  1.4200 +      // info.notificationType === 0, which refers to code1 defined in 3GPP
  1.4201 +      // 27.007 7.17. We only support partial MT unsolicited result code,
  1.4202 +      // referring to code2, for now.
  1.4203 +      return;
  1.4204 +    }
  1.4205 +
  1.4206 +    let notification = null;
  1.4207 +    let callIndex = -1;
  1.4208 +
  1.4209 +    switch (info.code) {
  1.4210 +      case SUPP_SVC_NOTIFICATION_CODE2_PUT_ON_HOLD:
  1.4211 +      case SUPP_SVC_NOTIFICATION_CODE2_RETRIEVED:
  1.4212 +        notification = GECKO_SUPP_SVC_NOTIFICATION_FROM_CODE2[info.code];
  1.4213 +        break;
  1.4214 +      default:
  1.4215 +        // Notification type not supported.
  1.4216 +        return;
  1.4217 +    }
  1.4218 +
  1.4219 +    // Get the target call object for this notification.
  1.4220 +    let currentCallIndexes = Object.keys(this.currentCalls);
  1.4221 +    if (currentCallIndexes.length === 1) {
  1.4222 +      // Only one call exists. This should be the target.
  1.4223 +      callIndex = currentCallIndexes[0];
  1.4224 +    } else {
  1.4225 +      // Find the call in |currentCalls| by the given number.
  1.4226 +      if (info.number) {
  1.4227 +        for each (let currentCall in this.currentCalls) {
  1.4228 +          if (currentCall.number == info.number) {
  1.4229 +            callIndex = currentCall.callIndex;
  1.4230 +            break;
  1.4231 +          }
  1.4232 +        }
  1.4233 +      }
  1.4234 +    }
  1.4235 +
  1.4236 +    let message = {rilMessageType: "suppSvcNotification",
  1.4237 +                   notification: notification,
  1.4238 +                   callIndex: callIndex};
  1.4239 +    this.sendChromeMessage(message);
  1.4240 +  },
  1.4241 +
  1.4242 +  _cancelEmergencyCbModeTimeout: function() {
  1.4243 +    if (this._exitEmergencyCbModeTimeoutID) {
  1.4244 +      clearTimeout(this._exitEmergencyCbModeTimeoutID);
  1.4245 +      this._exitEmergencyCbModeTimeoutID = null;
  1.4246 +    }
  1.4247 +  },
  1.4248 +
  1.4249 +  _handleChangedEmergencyCbMode: function(active) {
  1.4250 +    this._isInEmergencyCbMode = active;
  1.4251 +
  1.4252 +    // Clear the existed timeout event.
  1.4253 +    this._cancelEmergencyCbModeTimeout();
  1.4254 +
  1.4255 +    // Start a new timeout event when entering the mode.
  1.4256 +    if (active) {
  1.4257 +      this._exitEmergencyCbModeTimeoutID = setTimeout(
  1.4258 +          this.exitEmergencyCbMode.bind(this), EMERGENCY_CB_MODE_TIMEOUT_MS);
  1.4259 +    }
  1.4260 +
  1.4261 +    let message = {rilMessageType: "emergencyCbModeChange",
  1.4262 +                   active: active,
  1.4263 +                   timeoutMs: EMERGENCY_CB_MODE_TIMEOUT_MS};
  1.4264 +    this.sendChromeMessage(message);
  1.4265 +  },
  1.4266 +
  1.4267 +  _processNetworks: function() {
  1.4268 +    let strings = this.context.Buf.readStringList();
  1.4269 +    let networks = [];
  1.4270 +
  1.4271 +    for (let i = 0; i < strings.length; i += 4) {
  1.4272 +      let network = {
  1.4273 +        longName: strings[i],
  1.4274 +        shortName: strings[i + 1],
  1.4275 +        mcc: null,
  1.4276 +        mnc: null,
  1.4277 +        state: null
  1.4278 +      };
  1.4279 +
  1.4280 +      let networkTuple = strings[i + 2];
  1.4281 +      try {
  1.4282 +        this._processNetworkTuple(networkTuple, network);
  1.4283 +      } catch (e) {
  1.4284 +        if (DEBUG) this.context.debug("Error processing operator tuple: " + e);
  1.4285 +      }
  1.4286 +
  1.4287 +      let state = strings[i + 3];
  1.4288 +      if (state === NETWORK_STATE_UNKNOWN) {
  1.4289 +        // TODO: looks like this might conflict in style with
  1.4290 +        // GECKO_NETWORK_STYLE_UNKNOWN / nsINetworkManager
  1.4291 +        state = GECKO_QAN_STATE_UNKNOWN;
  1.4292 +      }
  1.4293 +
  1.4294 +      network.state = state;
  1.4295 +      networks.push(network);
  1.4296 +    }
  1.4297 +    return networks;
  1.4298 +  },
  1.4299 +
  1.4300 +  /**
  1.4301 +   * The "numeric" portion of the operator info is a tuple
  1.4302 +   * containing MCC (country code) and MNC (network code).
  1.4303 +   * AFAICT, MCC should always be 3 digits, making the remaining
  1.4304 +   * portion the MNC.
  1.4305 +   */
  1.4306 +  _processNetworkTuple: function(networkTuple, network) {
  1.4307 +    let tupleLen = networkTuple.length;
  1.4308 +
  1.4309 +    if (tupleLen == 5 || tupleLen == 6) {
  1.4310 +      network.mcc = networkTuple.substr(0, 3);
  1.4311 +      network.mnc = networkTuple.substr(3);
  1.4312 +    } else {
  1.4313 +      network.mcc = null;
  1.4314 +      network.mnc = null;
  1.4315 +
  1.4316 +      throw new Error("Invalid network tuple (should be 5 or 6 digits): " + networkTuple);
  1.4317 +    }
  1.4318 +  },
  1.4319 +
  1.4320 +  /**
  1.4321 +   * Check if GSM radio access technology group.
  1.4322 +   */
  1.4323 +  _isGsmTechGroup: function(radioTech) {
  1.4324 +    if (!radioTech) {
  1.4325 +      return true;
  1.4326 +    }
  1.4327 +
  1.4328 +    switch(radioTech) {
  1.4329 +      case NETWORK_CREG_TECH_GPRS:
  1.4330 +      case NETWORK_CREG_TECH_EDGE:
  1.4331 +      case NETWORK_CREG_TECH_UMTS:
  1.4332 +      case NETWORK_CREG_TECH_HSDPA:
  1.4333 +      case NETWORK_CREG_TECH_HSUPA:
  1.4334 +      case NETWORK_CREG_TECH_HSPA:
  1.4335 +      case NETWORK_CREG_TECH_LTE:
  1.4336 +      case NETWORK_CREG_TECH_HSPAP:
  1.4337 +      case NETWORK_CREG_TECH_GSM:
  1.4338 +        return true;
  1.4339 +    }
  1.4340 +
  1.4341 +    return false;
  1.4342 +  },
  1.4343 +
  1.4344 +  /**
  1.4345 +   * Process radio technology change.
  1.4346 +   */
  1.4347 +  _processRadioTech: function(radioTech) {
  1.4348 +    let isCdma = !this._isGsmTechGroup(radioTech);
  1.4349 +    this.radioTech = radioTech;
  1.4350 +
  1.4351 +    if (DEBUG) {
  1.4352 +      this.context.debug("Radio tech is set to: " + GECKO_RADIO_TECH[radioTech] +
  1.4353 +                         ", it is a " + (isCdma?"cdma":"gsm") + " technology");
  1.4354 +    }
  1.4355 +
  1.4356 +    // We should request SIM information when
  1.4357 +    //  1. Radio state has been changed, so we are waiting for radioTech or
  1.4358 +    //  2. isCdma is different from this._isCdma.
  1.4359 +    if (this._waitingRadioTech || isCdma != this._isCdma) {
  1.4360 +      this._isCdma = isCdma;
  1.4361 +      this._waitingRadioTech = false;
  1.4362 +      if (this._isCdma) {
  1.4363 +        this.getDeviceIdentity();
  1.4364 +      } else {
  1.4365 +        this.getIMEI();
  1.4366 +        this.getIMEISV();
  1.4367 +      }
  1.4368 +      this.getICCStatus();
  1.4369 +    }
  1.4370 +  },
  1.4371 +
  1.4372 +  /**
  1.4373 +   * Helper for returning the TOA for the given dial string.
  1.4374 +   */
  1.4375 +  _toaFromString: function(number) {
  1.4376 +    let toa = TOA_UNKNOWN;
  1.4377 +    if (number && number.length > 0 && number[0] == '+') {
  1.4378 +      toa = TOA_INTERNATIONAL;
  1.4379 +    }
  1.4380 +    return toa;
  1.4381 +  },
  1.4382 +
  1.4383 +  /**
  1.4384 +   * Helper for translating basic service group to call forwarding service class
  1.4385 +   * parameter.
  1.4386 +   */
  1.4387 +  _siToServiceClass: function(si) {
  1.4388 +    if (!si) {
  1.4389 +      return ICC_SERVICE_CLASS_NONE;
  1.4390 +    }
  1.4391 +
  1.4392 +    let serviceCode = parseInt(si, 10);
  1.4393 +    switch (serviceCode) {
  1.4394 +      case 10:
  1.4395 +        return ICC_SERVICE_CLASS_SMS + ICC_SERVICE_CLASS_FAX  + ICC_SERVICE_CLASS_VOICE;
  1.4396 +      case 11:
  1.4397 +        return ICC_SERVICE_CLASS_VOICE;
  1.4398 +      case 12:
  1.4399 +        return ICC_SERVICE_CLASS_SMS + ICC_SERVICE_CLASS_FAX;
  1.4400 +      case 13:
  1.4401 +        return ICC_SERVICE_CLASS_FAX;
  1.4402 +      case 16:
  1.4403 +        return ICC_SERVICE_CLASS_SMS;
  1.4404 +      case 19:
  1.4405 +        return ICC_SERVICE_CLASS_FAX + ICC_SERVICE_CLASS_VOICE;
  1.4406 +      case 21:
  1.4407 +        return ICC_SERVICE_CLASS_PAD + ICC_SERVICE_CLASS_DATA_ASYNC;
  1.4408 +      case 22:
  1.4409 +        return ICC_SERVICE_CLASS_PACKET + ICC_SERVICE_CLASS_DATA_SYNC;
  1.4410 +      case 25:
  1.4411 +        return ICC_SERVICE_CLASS_DATA_ASYNC;
  1.4412 +      case 26:
  1.4413 +        return ICC_SERVICE_CLASS_DATA_SYNC + SERVICE_CLASS_VOICE;
  1.4414 +      case 99:
  1.4415 +        return ICC_SERVICE_CLASS_PACKET;
  1.4416 +      default:
  1.4417 +        return ICC_SERVICE_CLASS_NONE;
  1.4418 +    }
  1.4419 +  },
  1.4420 +
  1.4421 +  /**
  1.4422 +   * @param message A decoded SMS-DELIVER message.
  1.4423 +   *
  1.4424 +   * @see 3GPP TS 31.111 section 7.1.1
  1.4425 +   */
  1.4426 +  dataDownloadViaSMSPP: function(message) {
  1.4427 +    let Buf = this.context.Buf;
  1.4428 +    let GsmPDUHelper = this.context.GsmPDUHelper;
  1.4429 +
  1.4430 +    let options = {
  1.4431 +      pid: message.pid,
  1.4432 +      dcs: message.dcs,
  1.4433 +      encoding: message.encoding,
  1.4434 +    };
  1.4435 +    Buf.newParcel(REQUEST_STK_SEND_ENVELOPE_WITH_STATUS, options);
  1.4436 +
  1.4437 +    Buf.seekIncoming(-1 * (Buf.getCurrentParcelSize() - Buf.getReadAvailable()
  1.4438 +                           - 2 * Buf.UINT32_SIZE)); // Skip response_type & request_type.
  1.4439 +    let messageStringLength = Buf.readInt32(); // In semi-octets
  1.4440 +    let smscLength = GsmPDUHelper.readHexOctet(); // In octets, inclusive of TOA
  1.4441 +    let tpduLength = (messageStringLength / 2) - (smscLength + 1); // In octets
  1.4442 +
  1.4443 +    // Device identities: 4 bytes
  1.4444 +    // Address: 0 or (2 + smscLength)
  1.4445 +    // SMS TPDU: (2 or 3) + tpduLength
  1.4446 +    let berLen = 4 +
  1.4447 +                 (smscLength ? (2 + smscLength) : 0) +
  1.4448 +                 (tpduLength <= 127 ? 2 : 3) + tpduLength; // In octets
  1.4449 +
  1.4450 +    let parcelLength = (berLen <= 127 ? 2 : 3) + berLen; // In octets
  1.4451 +    Buf.writeInt32(parcelLength * 2); // In semi-octets
  1.4452 +
  1.4453 +    // Write a BER-TLV
  1.4454 +    GsmPDUHelper.writeHexOctet(BER_SMS_PP_DOWNLOAD_TAG);
  1.4455 +    if (berLen > 127) {
  1.4456 +      GsmPDUHelper.writeHexOctet(0x81);
  1.4457 +    }
  1.4458 +    GsmPDUHelper.writeHexOctet(berLen);
  1.4459 +
  1.4460 +    // Device Identifies-TLV
  1.4461 +    GsmPDUHelper.writeHexOctet(COMPREHENSIONTLV_TAG_DEVICE_ID |
  1.4462 +                               COMPREHENSIONTLV_FLAG_CR);
  1.4463 +    GsmPDUHelper.writeHexOctet(0x02);
  1.4464 +    GsmPDUHelper.writeHexOctet(STK_DEVICE_ID_NETWORK);
  1.4465 +    GsmPDUHelper.writeHexOctet(STK_DEVICE_ID_SIM);
  1.4466 +
  1.4467 +    // Address-TLV
  1.4468 +    if (smscLength) {
  1.4469 +      GsmPDUHelper.writeHexOctet(COMPREHENSIONTLV_TAG_ADDRESS);
  1.4470 +      GsmPDUHelper.writeHexOctet(smscLength);
  1.4471 +      Buf.copyIncomingToOutgoing(Buf.PDU_HEX_OCTET_SIZE * smscLength);
  1.4472 +    }
  1.4473 +
  1.4474 +    // SMS TPDU-TLV
  1.4475 +    GsmPDUHelper.writeHexOctet(COMPREHENSIONTLV_TAG_SMS_TPDU |
  1.4476 +                               COMPREHENSIONTLV_FLAG_CR);
  1.4477 +    if (tpduLength > 127) {
  1.4478 +      GsmPDUHelper.writeHexOctet(0x81);
  1.4479 +    }
  1.4480 +    GsmPDUHelper.writeHexOctet(tpduLength);
  1.4481 +    Buf.copyIncomingToOutgoing(Buf.PDU_HEX_OCTET_SIZE * tpduLength);
  1.4482 +
  1.4483 +    // Write 2 string delimitors for the total string length must be even.
  1.4484 +    Buf.writeStringDelimiter(0);
  1.4485 +
  1.4486 +    Buf.sendParcel();
  1.4487 +  },
  1.4488 +
  1.4489 +  /**
  1.4490 +   * @param success A boolean value indicating the result of previous
  1.4491 +   *                SMS-DELIVER message handling.
  1.4492 +   * @param responsePduLen ICC IO response PDU length in octets.
  1.4493 +   * @param options An object that contains four attributes: `pid`, `dcs`,
  1.4494 +   *                `encoding` and `responsePduLen`.
  1.4495 +   *
  1.4496 +   * @see 3GPP TS 23.040 section 9.2.2.1a
  1.4497 +   */
  1.4498 +  acknowledgeIncomingGsmSmsWithPDU: function(success, responsePduLen, options) {
  1.4499 +    let Buf = this.context.Buf;
  1.4500 +    let GsmPDUHelper = this.context.GsmPDUHelper;
  1.4501 +
  1.4502 +    Buf.newParcel(REQUEST_ACKNOWLEDGE_INCOMING_GSM_SMS_WITH_PDU);
  1.4503 +
  1.4504 +    // Two strings.
  1.4505 +    Buf.writeInt32(2);
  1.4506 +
  1.4507 +    // String 1: Success
  1.4508 +    Buf.writeString(success ? "1" : "0");
  1.4509 +
  1.4510 +    // String 2: RP-ACK/RP-ERROR PDU
  1.4511 +    Buf.writeInt32(2 * (responsePduLen + (success ? 5 : 6))); // In semi-octet
  1.4512 +    // 1. TP-MTI & TP-UDHI
  1.4513 +    GsmPDUHelper.writeHexOctet(PDU_MTI_SMS_DELIVER);
  1.4514 +    if (!success) {
  1.4515 +      // 2. TP-FCS
  1.4516 +      GsmPDUHelper.writeHexOctet(PDU_FCS_USIM_DATA_DOWNLOAD_ERROR);
  1.4517 +    }
  1.4518 +    // 3. TP-PI
  1.4519 +    GsmPDUHelper.writeHexOctet(PDU_PI_USER_DATA_LENGTH |
  1.4520 +                               PDU_PI_DATA_CODING_SCHEME |
  1.4521 +                               PDU_PI_PROTOCOL_IDENTIFIER);
  1.4522 +    // 4. TP-PID
  1.4523 +    GsmPDUHelper.writeHexOctet(options.pid);
  1.4524 +    // 5. TP-DCS
  1.4525 +    GsmPDUHelper.writeHexOctet(options.dcs);
  1.4526 +    // 6. TP-UDL
  1.4527 +    if (options.encoding == PDU_DCS_MSG_CODING_7BITS_ALPHABET) {
  1.4528 +      GsmPDUHelper.writeHexOctet(Math.floor(responsePduLen * 8 / 7));
  1.4529 +    } else {
  1.4530 +      GsmPDUHelper.writeHexOctet(responsePduLen);
  1.4531 +    }
  1.4532 +    // TP-UD
  1.4533 +    Buf.copyIncomingToOutgoing(Buf.PDU_HEX_OCTET_SIZE * responsePduLen);
  1.4534 +    // Write 2 string delimitors for the total string length must be even.
  1.4535 +    Buf.writeStringDelimiter(0);
  1.4536 +
  1.4537 +    Buf.sendParcel();
  1.4538 +  },
  1.4539 +
  1.4540 +  /**
  1.4541 +   * @param message A decoded SMS-DELIVER message.
  1.4542 +   */
  1.4543 +  writeSmsToSIM: function(message) {
  1.4544 +    let Buf = this.context.Buf;
  1.4545 +    let GsmPDUHelper = this.context.GsmPDUHelper;
  1.4546 +
  1.4547 +    Buf.newParcel(REQUEST_WRITE_SMS_TO_SIM);
  1.4548 +
  1.4549 +    // Write EFsms Status
  1.4550 +    Buf.writeInt32(EFSMS_STATUS_FREE);
  1.4551 +
  1.4552 +    Buf.seekIncoming(-1 * (Buf.getCurrentParcelSize() - Buf.getReadAvailable()
  1.4553 +                           - 2 * Buf.UINT32_SIZE)); // Skip response_type & request_type.
  1.4554 +    let messageStringLength = Buf.readInt32(); // In semi-octets
  1.4555 +    let smscLength = GsmPDUHelper.readHexOctet(); // In octets, inclusive of TOA
  1.4556 +    let pduLength = (messageStringLength / 2) - (smscLength + 1); // In octets
  1.4557 +
  1.4558 +    // 1. Write PDU first.
  1.4559 +    if (smscLength > 0) {
  1.4560 +      Buf.seekIncoming(smscLength * Buf.PDU_HEX_OCTET_SIZE);
  1.4561 +    }
  1.4562 +    // Write EFsms PDU string length
  1.4563 +    Buf.writeInt32(2 * pduLength); // In semi-octets
  1.4564 +    if (pduLength) {
  1.4565 +      Buf.copyIncomingToOutgoing(Buf.PDU_HEX_OCTET_SIZE * pduLength);
  1.4566 +    }
  1.4567 +    // Write 2 string delimitors for the total string length must be even.
  1.4568 +    Buf.writeStringDelimiter(0);
  1.4569 +
  1.4570 +    // 2. Write SMSC
  1.4571 +    // Write EFsms SMSC string length
  1.4572 +    Buf.writeInt32(2 * (smscLength + 1)); // Plus smscLength itself, in semi-octets
  1.4573 +    // Write smscLength
  1.4574 +    GsmPDUHelper.writeHexOctet(smscLength);
  1.4575 +    // Write TOA & SMSC Address
  1.4576 +    if (smscLength) {
  1.4577 +      Buf.seekIncoming(-1 * (Buf.getCurrentParcelSize() - Buf.getReadAvailable()
  1.4578 +                             - 2 * Buf.UINT32_SIZE // Skip response_type, request_type.
  1.4579 +                             - 2 * Buf.PDU_HEX_OCTET_SIZE)); // Skip messageStringLength & smscLength.
  1.4580 +      Buf.copyIncomingToOutgoing(Buf.PDU_HEX_OCTET_SIZE * smscLength);
  1.4581 +    }
  1.4582 +    // Write 2 string delimitors for the total string length must be even.
  1.4583 +    Buf.writeStringDelimiter(0);
  1.4584 +
  1.4585 +    Buf.sendParcel();
  1.4586 +  },
  1.4587 +
  1.4588 +  /**
  1.4589 +   * Helper to delegate the received sms segment to RadioInterface to process.
  1.4590 +   *
  1.4591 +   * @param message
  1.4592 +   *        Received sms message.
  1.4593 +   *
  1.4594 +   * @return MOZ_FCS_WAIT_FOR_EXPLICIT_ACK
  1.4595 +   */
  1.4596 +  _processSmsMultipart: function(message) {
  1.4597 +    message.rilMessageType = "sms-received";
  1.4598 +
  1.4599 +    this.sendChromeMessage(message);
  1.4600 +
  1.4601 +    return MOZ_FCS_WAIT_FOR_EXPLICIT_ACK;
  1.4602 +  },
  1.4603 +
  1.4604 +  /**
  1.4605 +   * Helper for processing SMS-STATUS-REPORT PDUs.
  1.4606 +   *
  1.4607 +   * @param length
  1.4608 +   *        Length of SMS string in the incoming parcel.
  1.4609 +   *
  1.4610 +   * @return A failure cause defined in 3GPP 23.040 clause 9.2.3.22.
  1.4611 +   */
  1.4612 +  _processSmsStatusReport: function(length) {
  1.4613 +    let [message, result] = this.context.GsmPDUHelper.processReceivedSms(length);
  1.4614 +    if (!message) {
  1.4615 +      if (DEBUG) this.context.debug("invalid SMS-STATUS-REPORT");
  1.4616 +      return PDU_FCS_UNSPECIFIED;
  1.4617 +    }
  1.4618 +
  1.4619 +    let options = this._pendingSentSmsMap[message.messageRef];
  1.4620 +    if (!options) {
  1.4621 +      if (DEBUG) this.context.debug("no pending SMS-SUBMIT message");
  1.4622 +      return PDU_FCS_OK;
  1.4623 +    }
  1.4624 +
  1.4625 +    let status = message.status;
  1.4626 +
  1.4627 +    // 3GPP TS 23.040 9.2.3.15 `The MS shall interpret any reserved values as
  1.4628 +    // "Service Rejected"(01100011) but shall store them exactly as received.`
  1.4629 +    if ((status >= 0x80)
  1.4630 +        || ((status >= PDU_ST_0_RESERVED_BEGIN)
  1.4631 +            && (status < PDU_ST_0_SC_SPECIFIC_BEGIN))
  1.4632 +        || ((status >= PDU_ST_1_RESERVED_BEGIN)
  1.4633 +            && (status < PDU_ST_1_SC_SPECIFIC_BEGIN))
  1.4634 +        || ((status >= PDU_ST_2_RESERVED_BEGIN)
  1.4635 +            && (status < PDU_ST_2_SC_SPECIFIC_BEGIN))
  1.4636 +        || ((status >= PDU_ST_3_RESERVED_BEGIN)
  1.4637 +            && (status < PDU_ST_3_SC_SPECIFIC_BEGIN))
  1.4638 +        ) {
  1.4639 +      status = PDU_ST_3_SERVICE_REJECTED;
  1.4640 +    }
  1.4641 +
  1.4642 +    // Pending. Waiting for next status report.
  1.4643 +    if ((status >>> 5) == 0x01) {
  1.4644 +      if (DEBUG) this.context.debug("SMS-STATUS-REPORT: delivery still pending");
  1.4645 +      return PDU_FCS_OK;
  1.4646 +    }
  1.4647 +
  1.4648 +    delete this._pendingSentSmsMap[message.messageRef];
  1.4649 +
  1.4650 +    let deliveryStatus = ((status >>> 5) === 0x00)
  1.4651 +                       ? GECKO_SMS_DELIVERY_STATUS_SUCCESS
  1.4652 +                       : GECKO_SMS_DELIVERY_STATUS_ERROR;
  1.4653 +    this.sendChromeMessage({
  1.4654 +      rilMessageType: options.rilMessageType,
  1.4655 +      rilMessageToken: options.rilMessageToken,
  1.4656 +      deliveryStatus: deliveryStatus
  1.4657 +    });
  1.4658 +
  1.4659 +    return PDU_FCS_OK;
  1.4660 +  },
  1.4661 +
  1.4662 +  /**
  1.4663 +   * Helper for processing CDMA SMS Delivery Acknowledgment Message
  1.4664 +   *
  1.4665 +   * @param message
  1.4666 +   *        decoded SMS Delivery ACK message from CdmaPDUHelper.
  1.4667 +   *
  1.4668 +   * @return A failure cause defined in 3GPP 23.040 clause 9.2.3.22.
  1.4669 +   */
  1.4670 +  _processCdmaSmsStatusReport: function(message) {
  1.4671 +    let options = this._pendingSentSmsMap[message.msgId];
  1.4672 +    if (!options) {
  1.4673 +      if (DEBUG) this.context.debug("no pending SMS-SUBMIT message");
  1.4674 +      return PDU_FCS_OK;
  1.4675 +    }
  1.4676 +
  1.4677 +    if (message.errorClass === 2) {
  1.4678 +      if (DEBUG) {
  1.4679 +        this.context.debug("SMS-STATUS-REPORT: delivery still pending, " +
  1.4680 +                           "msgStatus: " + message.msgStatus);
  1.4681 +      }
  1.4682 +      return PDU_FCS_OK;
  1.4683 +    }
  1.4684 +
  1.4685 +    delete this._pendingSentSmsMap[message.msgId];
  1.4686 +
  1.4687 +    if (message.errorClass === -1 && message.body) {
  1.4688 +      // Process as normal incoming SMS, if errorClass is invalid
  1.4689 +      // but message body is available.
  1.4690 +      return this._processSmsMultipart(message);
  1.4691 +    }
  1.4692 +
  1.4693 +    let deliveryStatus = (message.errorClass === 0)
  1.4694 +                       ? GECKO_SMS_DELIVERY_STATUS_SUCCESS
  1.4695 +                       : GECKO_SMS_DELIVERY_STATUS_ERROR;
  1.4696 +    this.sendChromeMessage({
  1.4697 +      rilMessageType: options.rilMessageType,
  1.4698 +      rilMessageToken: options.rilMessageToken,
  1.4699 +      deliveryStatus: deliveryStatus
  1.4700 +    });
  1.4701 +
  1.4702 +    return PDU_FCS_OK;
  1.4703 +  },
  1.4704 +
  1.4705 +  /**
  1.4706 +   * Helper for processing CDMA SMS WAP Push Message
  1.4707 +   *
  1.4708 +   * @param message
  1.4709 +   *        decoded WAP message from CdmaPDUHelper.
  1.4710 +   *
  1.4711 +   * @return A failure cause defined in 3GPP 23.040 clause 9.2.3.22.
  1.4712 +   */
  1.4713 +  _processCdmaSmsWapPush: function(message) {
  1.4714 +    if (!message.data) {
  1.4715 +      if (DEBUG) this.context.debug("no data inside WAP Push message.");
  1.4716 +      return PDU_FCS_OK;
  1.4717 +    }
  1.4718 +
  1.4719 +    // See 6.5. MAPPING OF WDP TO CDMA SMS in WAP-295-WDP.
  1.4720 +    //
  1.4721 +    // Field             | Length (bits)
  1.4722 +    // -----------------------------------------
  1.4723 +    // MSG_TYPE          | 8
  1.4724 +    // TOTAL_SEGMENTS    | 8
  1.4725 +    // SEGMENT_NUMBER    | 8
  1.4726 +    // DATAGRAM          | (NUM_FIELDS – 3) * 8
  1.4727 +    let index = 0;
  1.4728 +    if (message.data[index++] !== 0) {
  1.4729 +      if (DEBUG) this.context.debug("Ignore a WAP Message which is not WDP.");
  1.4730 +      return PDU_FCS_OK;
  1.4731 +    }
  1.4732 +
  1.4733 +    // 1. Originator Address in SMS-TL + Message_Id in SMS-TS are used to identify a unique WDP datagram.
  1.4734 +    // 2. TOTAL_SEGMENTS, SEGMENT_NUMBER are used to verify that a complete
  1.4735 +    //    datagram has been received and is ready to be passed to a higher layer.
  1.4736 +    message.header = {
  1.4737 +      segmentRef:     message.msgId,
  1.4738 +      segmentMaxSeq:  message.data[index++],
  1.4739 +      segmentSeq:     message.data[index++] + 1 // It's zero-based in CDMA WAP Push.
  1.4740 +    };
  1.4741 +
  1.4742 +    if (message.header.segmentSeq > message.header.segmentMaxSeq) {
  1.4743 +      if (DEBUG) this.context.debug("Wrong WDP segment info.");
  1.4744 +      return PDU_FCS_OK;
  1.4745 +    }
  1.4746 +
  1.4747 +    // Ports are only specified in 1st segment.
  1.4748 +    if (message.header.segmentSeq == 1) {
  1.4749 +      message.header.originatorPort = message.data[index++] << 8;
  1.4750 +      message.header.originatorPort |= message.data[index++];
  1.4751 +      message.header.destinationPort = message.data[index++] << 8;
  1.4752 +      message.header.destinationPort |= message.data[index++];
  1.4753 +    }
  1.4754 +
  1.4755 +    message.data = message.data.subarray(index);
  1.4756 +
  1.4757 +    return this._processSmsMultipart(message);
  1.4758 +  },
  1.4759 +
  1.4760 +  /**
  1.4761 +   * Helper for processing sent multipart SMS.
  1.4762 +   */
  1.4763 +  _processSentSmsSegment: function(options) {
  1.4764 +    // Setup attributes for sending next segment
  1.4765 +    let next = options.segmentSeq;
  1.4766 +    options.body = options.segments[next].body;
  1.4767 +    options.encodedBodyLength = options.segments[next].encodedBodyLength;
  1.4768 +    options.segmentSeq = next + 1;
  1.4769 +
  1.4770 +    this.sendSMS(options);
  1.4771 +  },
  1.4772 +
  1.4773 +  /**
  1.4774 +   * Helper for processing result of send SMS.
  1.4775 +   *
  1.4776 +   * @param length
  1.4777 +   *        Length of SMS string in the incoming parcel.
  1.4778 +   * @param options
  1.4779 +   *        Sms information.
  1.4780 +   */
  1.4781 +  _processSmsSendResult: function(length, options) {
  1.4782 +    if (options.rilRequestError) {
  1.4783 +      if (DEBUG) {
  1.4784 +        this.context.debug("_processSmsSendResult: rilRequestError = " +
  1.4785 +                           options.rilRequestError);
  1.4786 +      }
  1.4787 +      switch (options.rilRequestError) {
  1.4788 +        case ERROR_SMS_SEND_FAIL_RETRY:
  1.4789 +          if (options.retryCount < SMS_RETRY_MAX) {
  1.4790 +            options.retryCount++;
  1.4791 +            // TODO: bug 736702 TP-MR, retry interval, retry timeout
  1.4792 +            this.sendSMS(options);
  1.4793 +            break;
  1.4794 +          }
  1.4795 +          // Fallback to default error handling if it meets max retry count.
  1.4796 +          // Fall through.
  1.4797 +        default:
  1.4798 +          this.sendChromeMessage({
  1.4799 +            rilMessageType: options.rilMessageType,
  1.4800 +            rilMessageToken: options.rilMessageToken,
  1.4801 +            errorMsg: options.rilRequestError,
  1.4802 +          });
  1.4803 +          break;
  1.4804 +      }
  1.4805 +      return;
  1.4806 +    }
  1.4807 +
  1.4808 +    let Buf = this.context.Buf;
  1.4809 +    options.messageRef = Buf.readInt32();
  1.4810 +    options.ackPDU = Buf.readString();
  1.4811 +    options.errorCode = Buf.readInt32();
  1.4812 +
  1.4813 +    if ((options.segmentMaxSeq > 1)
  1.4814 +        && (options.segmentSeq < options.segmentMaxSeq)) {
  1.4815 +      // Not last segment
  1.4816 +      this._processSentSmsSegment(options);
  1.4817 +    } else {
  1.4818 +      // Last segment sent with success.
  1.4819 +      if (options.requestStatusReport) {
  1.4820 +        if (DEBUG) {
  1.4821 +          this.context.debug("waiting SMS-STATUS-REPORT for messageRef " +
  1.4822 +                             options.messageRef);
  1.4823 +        }
  1.4824 +        this._pendingSentSmsMap[options.messageRef] = options;
  1.4825 +      }
  1.4826 +
  1.4827 +      this.sendChromeMessage({
  1.4828 +        rilMessageType: options.rilMessageType,
  1.4829 +        rilMessageToken: options.rilMessageToken,
  1.4830 +      });
  1.4831 +    }
  1.4832 +  },
  1.4833 +
  1.4834 +  _processReceivedSmsCbPage: function(original) {
  1.4835 +    if (original.numPages <= 1) {
  1.4836 +      if (original.body) {
  1.4837 +        original.fullBody = original.body;
  1.4838 +        delete original.body;
  1.4839 +      } else if (original.data) {
  1.4840 +        original.fullData = original.data;
  1.4841 +        delete original.data;
  1.4842 +      }
  1.4843 +      return original;
  1.4844 +    }
  1.4845 +
  1.4846 +    // Hash = <serial>:<mcc>:<mnc>:<lac>:<cid>
  1.4847 +    let hash = original.serial + ":" + this.iccInfo.mcc + ":"
  1.4848 +               + this.iccInfo.mnc + ":";
  1.4849 +    switch (original.geographicalScope) {
  1.4850 +      case CB_GSM_GEOGRAPHICAL_SCOPE_CELL_WIDE_IMMEDIATE:
  1.4851 +      case CB_GSM_GEOGRAPHICAL_SCOPE_CELL_WIDE:
  1.4852 +        hash += this.voiceRegistrationState.cell.gsmLocationAreaCode + ":"
  1.4853 +             + this.voiceRegistrationState.cell.gsmCellId;
  1.4854 +        break;
  1.4855 +      case CB_GSM_GEOGRAPHICAL_SCOPE_LOCATION_AREA_WIDE:
  1.4856 +        hash += this.voiceRegistrationState.cell.gsmLocationAreaCode + ":";
  1.4857 +        break;
  1.4858 +      default:
  1.4859 +        hash += ":";
  1.4860 +        break;
  1.4861 +    }
  1.4862 +
  1.4863 +    let index = original.pageIndex;
  1.4864 +
  1.4865 +    let options = this._receivedSmsCbPagesMap[hash];
  1.4866 +    if (!options) {
  1.4867 +      options = original;
  1.4868 +      this._receivedSmsCbPagesMap[hash] = options;
  1.4869 +
  1.4870 +      options.receivedPages = 0;
  1.4871 +      options.pages = [];
  1.4872 +    } else if (options.pages[index]) {
  1.4873 +      // Duplicated page?
  1.4874 +      if (DEBUG) {
  1.4875 +        this.context.debug("Got duplicated page no." + index +
  1.4876 +                           " of a multipage SMSCB: " + JSON.stringify(original));
  1.4877 +      }
  1.4878 +      return null;
  1.4879 +    }
  1.4880 +
  1.4881 +    if (options.encoding == PDU_DCS_MSG_CODING_8BITS_ALPHABET) {
  1.4882 +      options.pages[index] = original.data;
  1.4883 +      delete original.data;
  1.4884 +    } else {
  1.4885 +      options.pages[index] = original.body;
  1.4886 +      delete original.body;
  1.4887 +    }
  1.4888 +    options.receivedPages++;
  1.4889 +    if (options.receivedPages < options.numPages) {
  1.4890 +      if (DEBUG) {
  1.4891 +        this.context.debug("Got page no." + index + " of a multipage SMSCB: " +
  1.4892 +                           JSON.stringify(options));
  1.4893 +      }
  1.4894 +      return null;
  1.4895 +    }
  1.4896 +
  1.4897 +    // Remove from map
  1.4898 +    delete this._receivedSmsCbPagesMap[hash];
  1.4899 +
  1.4900 +    // Rebuild full body
  1.4901 +    if (options.encoding == PDU_DCS_MSG_CODING_8BITS_ALPHABET) {
  1.4902 +      // Uint8Array doesn't have `concat`, so we have to merge all pages by hand.
  1.4903 +      let fullDataLen = 0;
  1.4904 +      for (let i = 1; i <= options.numPages; i++) {
  1.4905 +        fullDataLen += options.pages[i].length;
  1.4906 +      }
  1.4907 +
  1.4908 +      options.fullData = new Uint8Array(fullDataLen);
  1.4909 +      for (let d= 0, i = 1; i <= options.numPages; i++) {
  1.4910 +        let data = options.pages[i];
  1.4911 +        for (let j = 0; j < data.length; j++) {
  1.4912 +          options.fullData[d++] = data[j];
  1.4913 +        }
  1.4914 +      }
  1.4915 +    } else {
  1.4916 +      options.fullBody = options.pages.join("");
  1.4917 +    }
  1.4918 +
  1.4919 +    if (DEBUG) {
  1.4920 +      this.context.debug("Got full multipage SMSCB: " + JSON.stringify(options));
  1.4921 +    }
  1.4922 +
  1.4923 +    return options;
  1.4924 +  },
  1.4925 +
  1.4926 +  _mergeCellBroadcastConfigs: function(list, from, to) {
  1.4927 +    if (!list) {
  1.4928 +      return [from, to];
  1.4929 +    }
  1.4930 +
  1.4931 +    for (let i = 0, f1, t1; i < list.length;) {
  1.4932 +      f1 = list[i++];
  1.4933 +      t1 = list[i++];
  1.4934 +      if (to == f1) {
  1.4935 +        // ...[from]...[to|f1]...(t1)
  1.4936 +        list[i - 2] = from;
  1.4937 +        return list;
  1.4938 +      }
  1.4939 +
  1.4940 +      if (to < f1) {
  1.4941 +        // ...[from]...(to)...[f1] or ...[from]...(to)[f1]
  1.4942 +        if (i > 2) {
  1.4943 +          // Not the first range pair, merge three arrays.
  1.4944 +          return list.slice(0, i - 2).concat([from, to]).concat(list.slice(i - 2));
  1.4945 +        } else {
  1.4946 +          return [from, to].concat(list);
  1.4947 +        }
  1.4948 +      }
  1.4949 +
  1.4950 +      if (from > t1) {
  1.4951 +        // ...[f1]...(t1)[from] or ...[f1]...(t1)...[from]
  1.4952 +        continue;
  1.4953 +      }
  1.4954 +
  1.4955 +      // Have overlap or merge-able adjacency with [f1]...(t1). Replace it
  1.4956 +      // with [min(from, f1)]...(max(to, t1)).
  1.4957 +
  1.4958 +      let changed = false;
  1.4959 +      if (from < f1) {
  1.4960 +        // [from]...[f1]...(t1) or [from][f1]...(t1)
  1.4961 +        // Save minimum from value.
  1.4962 +        list[i - 2] = from;
  1.4963 +        changed = true;
  1.4964 +      }
  1.4965 +
  1.4966 +      if (to <= t1) {
  1.4967 +        // [from]...[to](t1) or [from]...(to|t1)
  1.4968 +        // Can't have further merge-able adjacency. Return.
  1.4969 +        return list;
  1.4970 +      }
  1.4971 +
  1.4972 +      // Try merging possible next adjacent range.
  1.4973 +      let j = i;
  1.4974 +      for (let f2, t2; j < list.length;) {
  1.4975 +        f2 = list[j++];
  1.4976 +        t2 = list[j++];
  1.4977 +        if (to > t2) {
  1.4978 +          // [from]...[f2]...[t2]...(to) or [from]...[f2]...[t2](to)
  1.4979 +          // Merge next adjacent range again.
  1.4980 +          continue;
  1.4981 +        }
  1.4982 +
  1.4983 +        if (to < t2) {
  1.4984 +          if (to < f2) {
  1.4985 +            // [from]...(to)[f2] or [from]...(to)...[f2]
  1.4986 +            // Roll back and give up.
  1.4987 +            j -= 2;
  1.4988 +          } else if (to < t2) {
  1.4989 +            // [from]...[to|f2]...(t2), or [from]...[f2]...[to](t2)
  1.4990 +            // Merge to [from]...(t2) and give up.
  1.4991 +            to = t2;
  1.4992 +          }
  1.4993 +        }
  1.4994 +
  1.4995 +        break;
  1.4996 +      }
  1.4997 +
  1.4998 +      // Save maximum to value.
  1.4999 +      list[i - 1] = to;
  1.5000 +
  1.5001 +      if (j != i) {
  1.5002 +        // Remove merged adjacent ranges.
  1.5003 +        let ret = list.slice(0, i);
  1.5004 +        if (j < list.length) {
  1.5005 +          ret = ret.concat(list.slice(j));
  1.5006 +        }
  1.5007 +        return ret;
  1.5008 +      }
  1.5009 +
  1.5010 +      return list;
  1.5011 +    }
  1.5012 +
  1.5013 +    // Append to the end.
  1.5014 +    list.push(from);
  1.5015 +    list.push(to);
  1.5016 +
  1.5017 +    return list;
  1.5018 +  },
  1.5019 +
  1.5020 +  _isCellBroadcastConfigReady: function() {
  1.5021 +    if (!("MMI" in this.cellBroadcastConfigs)) {
  1.5022 +      return false;
  1.5023 +    }
  1.5024 +
  1.5025 +    // CBMI should be ready in GSM.
  1.5026 +    if (!this._isCdma &&
  1.5027 +        (!("CBMI" in this.cellBroadcastConfigs) ||
  1.5028 +         !("CBMID" in this.cellBroadcastConfigs) ||
  1.5029 +         !("CBMIR" in this.cellBroadcastConfigs))) {
  1.5030 +      return false;
  1.5031 +    }
  1.5032 +
  1.5033 +    return true;
  1.5034 +  },
  1.5035 +
  1.5036 +  /**
  1.5037 +   * Merge all members of cellBroadcastConfigs into mergedCellBroadcastConfig.
  1.5038 +   */
  1.5039 +  _mergeAllCellBroadcastConfigs: function() {
  1.5040 +    if (!this._isCellBroadcastConfigReady()) {
  1.5041 +      if (DEBUG) {
  1.5042 +        this.context.debug("cell broadcast configs not ready, waiting ...");
  1.5043 +      }
  1.5044 +      return;
  1.5045 +    }
  1.5046 +
  1.5047 +    // Prepare cell broadcast config. CBMI* are only used in GSM.
  1.5048 +    let usedCellBroadcastConfigs = {MMI: this.cellBroadcastConfigs.MMI};
  1.5049 +    if (!this._isCdma) {
  1.5050 +      usedCellBroadcastConfigs.CBMI = this.cellBroadcastConfigs.CBMI;
  1.5051 +      usedCellBroadcastConfigs.CBMID = this.cellBroadcastConfigs.CBMID;
  1.5052 +      usedCellBroadcastConfigs.CBMIR = this.cellBroadcastConfigs.CBMIR;
  1.5053 +    }
  1.5054 +
  1.5055 +    if (DEBUG) {
  1.5056 +      this.context.debug("Cell Broadcast search lists: " +
  1.5057 +                         JSON.stringify(usedCellBroadcastConfigs));
  1.5058 +    }
  1.5059 +
  1.5060 +    let list = null;
  1.5061 +    for each (let ll in usedCellBroadcastConfigs) {
  1.5062 +      if (ll == null) {
  1.5063 +        continue;
  1.5064 +      }
  1.5065 +
  1.5066 +      for (let i = 0; i < ll.length; i += 2) {
  1.5067 +        list = this._mergeCellBroadcastConfigs(list, ll[i], ll[i + 1]);
  1.5068 +      }
  1.5069 +    }
  1.5070 +
  1.5071 +    if (DEBUG) {
  1.5072 +      this.context.debug("Cell Broadcast search lists(merged): " +
  1.5073 +                         JSON.stringify(list));
  1.5074 +    }
  1.5075 +    this.mergedCellBroadcastConfig = list;
  1.5076 +    this.updateCellBroadcastConfig();
  1.5077 +  },
  1.5078 +
  1.5079 +  /**
  1.5080 +   * Check whether search list from settings is settable by MMI, that is,
  1.5081 +   * whether the range is bounded in any entries of CB_NON_MMI_SETTABLE_RANGES.
  1.5082 +   */
  1.5083 +  _checkCellBroadcastMMISettable: function(from, to) {
  1.5084 +    if ((to <= from) || (from >= 65536) || (from < 0)) {
  1.5085 +      return false;
  1.5086 +    }
  1.5087 +
  1.5088 +    if (!this._isCdma) {
  1.5089 +      // GSM not settable ranges.
  1.5090 +      for (let i = 0, f, t; i < CB_NON_MMI_SETTABLE_RANGES.length;) {
  1.5091 +        f = CB_NON_MMI_SETTABLE_RANGES[i++];
  1.5092 +        t = CB_NON_MMI_SETTABLE_RANGES[i++];
  1.5093 +        if ((from < t) && (to > f)) {
  1.5094 +          // Have overlap.
  1.5095 +          return false;
  1.5096 +        }
  1.5097 +      }
  1.5098 +    }
  1.5099 +
  1.5100 +    return true;
  1.5101 +  },
  1.5102 +
  1.5103 +  /**
  1.5104 +   * Convert Cell Broadcast settings string into search list.
  1.5105 +   */
  1.5106 +  _convertCellBroadcastSearchList: function(searchListStr) {
  1.5107 +    let parts = searchListStr && searchListStr.split(",");
  1.5108 +    if (!parts) {
  1.5109 +      return null;
  1.5110 +    }
  1.5111 +
  1.5112 +    let list = null;
  1.5113 +    let result, from, to;
  1.5114 +    for (let range of parts) {
  1.5115 +      // Match "12" or "12-34". The result will be ["12", "12", null] or
  1.5116 +      // ["12-34", "12", "34"].
  1.5117 +      result = range.match(/^(\d+)(?:-(\d+))?$/);
  1.5118 +      if (!result) {
  1.5119 +        throw "Invalid format";
  1.5120 +      }
  1.5121 +
  1.5122 +      from = parseInt(result[1], 10);
  1.5123 +      to = (result[2]) ? parseInt(result[2], 10) + 1 : from + 1;
  1.5124 +      if (!this._checkCellBroadcastMMISettable(from, to)) {
  1.5125 +        throw "Invalid range";
  1.5126 +      }
  1.5127 +
  1.5128 +      if (list == null) {
  1.5129 +        list = [];
  1.5130 +      }
  1.5131 +      list.push(from);
  1.5132 +      list.push(to);
  1.5133 +    }
  1.5134 +
  1.5135 +    return list;
  1.5136 +  },
  1.5137 +
  1.5138 +  /**
  1.5139 +   * Handle incoming messages from the main UI thread.
  1.5140 +   *
  1.5141 +   * @param message
  1.5142 +   *        Object containing the message. Messages are supposed
  1.5143 +   */
  1.5144 +  handleChromeMessage: function(message) {
  1.5145 +    if (DEBUG) {
  1.5146 +      this.context.debug("Received chrome message " + JSON.stringify(message));
  1.5147 +    }
  1.5148 +    let method = this[message.rilMessageType];
  1.5149 +    if (typeof method != "function") {
  1.5150 +      if (DEBUG) {
  1.5151 +        this.context.debug("Don't know what to do with message " +
  1.5152 +                           JSON.stringify(message));
  1.5153 +      }
  1.5154 +      return;
  1.5155 +    }
  1.5156 +    method.call(this, message);
  1.5157 +  },
  1.5158 +
  1.5159 +  /**
  1.5160 +   * Get a list of current voice calls.
  1.5161 +   */
  1.5162 +  enumerateCalls: function(options) {
  1.5163 +    if (DEBUG) this.context.debug("Sending all current calls");
  1.5164 +    let calls = [];
  1.5165 +    for each (let call in this.currentCalls) {
  1.5166 +      calls.push(call);
  1.5167 +    }
  1.5168 +    options.calls = calls;
  1.5169 +    this.sendChromeMessage(options);
  1.5170 +  },
  1.5171 +
  1.5172 +  /**
  1.5173 +   * Process STK Proactive Command.
  1.5174 +   */
  1.5175 +  processStkProactiveCommand: function() {
  1.5176 +    let Buf = this.context.Buf;
  1.5177 +    let length = Buf.readInt32();
  1.5178 +    let berTlv;
  1.5179 +    try {
  1.5180 +      berTlv = this.context.BerTlvHelper.decode(length / 2);
  1.5181 +    } catch (e) {
  1.5182 +      if (DEBUG) this.context.debug("processStkProactiveCommand : " + e);
  1.5183 +      this.sendStkTerminalResponse({
  1.5184 +        resultCode: STK_RESULT_CMD_DATA_NOT_UNDERSTOOD});
  1.5185 +      return;
  1.5186 +    }
  1.5187 +
  1.5188 +    Buf.readStringDelimiter(length);
  1.5189 +
  1.5190 +    let ctlvs = berTlv.value;
  1.5191 +    let ctlv = this.context.StkProactiveCmdHelper.searchForTag(
  1.5192 +        COMPREHENSIONTLV_TAG_COMMAND_DETAILS, ctlvs);
  1.5193 +    if (!ctlv) {
  1.5194 +      this.sendStkTerminalResponse({
  1.5195 +        resultCode: STK_RESULT_CMD_DATA_NOT_UNDERSTOOD});
  1.5196 +      throw new Error("Can't find COMMAND_DETAILS ComprehensionTlv");
  1.5197 +    }
  1.5198 +
  1.5199 +    let cmdDetails = ctlv.value;
  1.5200 +    if (DEBUG) {
  1.5201 +      this.context.debug("commandNumber = " + cmdDetails.commandNumber +
  1.5202 +                         " typeOfCommand = " + cmdDetails.typeOfCommand.toString(16) +
  1.5203 +                         " commandQualifier = " + cmdDetails.commandQualifier);
  1.5204 +    }
  1.5205 +
  1.5206 +    // STK_CMD_MORE_TIME need not to propagate event to chrome.
  1.5207 +    if (cmdDetails.typeOfCommand == STK_CMD_MORE_TIME) {
  1.5208 +      this.sendStkTerminalResponse({
  1.5209 +        command: cmdDetails,
  1.5210 +        resultCode: STK_RESULT_OK});
  1.5211 +      return;
  1.5212 +    }
  1.5213 +
  1.5214 +    cmdDetails.rilMessageType = "stkcommand";
  1.5215 +    cmdDetails.options =
  1.5216 +      this.context.StkCommandParamsFactory.createParam(cmdDetails, ctlvs);
  1.5217 +    this.sendChromeMessage(cmdDetails);
  1.5218 +  },
  1.5219 +
  1.5220 +  /**
  1.5221 +   * Send messages to the main thread.
  1.5222 +   */
  1.5223 +  sendChromeMessage: function(message) {
  1.5224 +    message.rilMessageClientId = this.context.clientId;
  1.5225 +    postMessage(message);
  1.5226 +  },
  1.5227 +
  1.5228 +  /**
  1.5229 +   * Handle incoming requests from the RIL. We find the method that
  1.5230 +   * corresponds to the request type. Incidentally, the request type
  1.5231 +   * _is_ the method name, so that's easy.
  1.5232 +   */
  1.5233 +
  1.5234 +  handleParcel: function(request_type, length, options) {
  1.5235 +    let method = this[request_type];
  1.5236 +    if (typeof method == "function") {
  1.5237 +      if (DEBUG) this.context.debug("Handling parcel as " + method.name);
  1.5238 +      method.call(this, length, options);
  1.5239 +    }
  1.5240 +  }
  1.5241 +};
  1.5242 +
  1.5243 +RilObject.prototype[REQUEST_GET_SIM_STATUS] = function REQUEST_GET_SIM_STATUS(length, options) {
  1.5244 +  if (options.rilRequestError) {
  1.5245 +    return;
  1.5246 +  }
  1.5247 +
  1.5248 +  let iccStatus = {};
  1.5249 +  let Buf = this.context.Buf;
  1.5250 +  iccStatus.cardState = Buf.readInt32(); // CARD_STATE_*
  1.5251 +  iccStatus.universalPINState = Buf.readInt32(); // CARD_PINSTATE_*
  1.5252 +  iccStatus.gsmUmtsSubscriptionAppIndex = Buf.readInt32();
  1.5253 +  iccStatus.cdmaSubscriptionAppIndex = Buf.readInt32();
  1.5254 +  if (!this.v5Legacy) {
  1.5255 +    iccStatus.imsSubscriptionAppIndex = Buf.readInt32();
  1.5256 +  }
  1.5257 +
  1.5258 +  let apps_length = Buf.readInt32();
  1.5259 +  if (apps_length > CARD_MAX_APPS) {
  1.5260 +    apps_length = CARD_MAX_APPS;
  1.5261 +  }
  1.5262 +
  1.5263 +  iccStatus.apps = [];
  1.5264 +  for (let i = 0 ; i < apps_length ; i++) {
  1.5265 +    iccStatus.apps.push({
  1.5266 +      app_type:       Buf.readInt32(), // CARD_APPTYPE_*
  1.5267 +      app_state:      Buf.readInt32(), // CARD_APPSTATE_*
  1.5268 +      perso_substate: Buf.readInt32(), // CARD_PERSOSUBSTATE_*
  1.5269 +      aid:            Buf.readString(),
  1.5270 +      app_label:      Buf.readString(),
  1.5271 +      pin1_replaced:  Buf.readInt32(),
  1.5272 +      pin1:           Buf.readInt32(),
  1.5273 +      pin2:           Buf.readInt32()
  1.5274 +    });
  1.5275 +    if (RILQUIRKS_SIM_APP_STATE_EXTRA_FIELDS) {
  1.5276 +      Buf.readInt32();
  1.5277 +      Buf.readInt32();
  1.5278 +      Buf.readInt32();
  1.5279 +      Buf.readInt32();
  1.5280 +    }
  1.5281 +  }
  1.5282 +
  1.5283 +  if (DEBUG) this.context.debug("iccStatus: " + JSON.stringify(iccStatus));
  1.5284 +  this._processICCStatus(iccStatus);
  1.5285 +};
  1.5286 +RilObject.prototype[REQUEST_ENTER_SIM_PIN] = function REQUEST_ENTER_SIM_PIN(length, options) {
  1.5287 +  this._processEnterAndChangeICCResponses(length, options);
  1.5288 +};
  1.5289 +RilObject.prototype[REQUEST_ENTER_SIM_PUK] = function REQUEST_ENTER_SIM_PUK(length, options) {
  1.5290 +  this._processEnterAndChangeICCResponses(length, options);
  1.5291 +};
  1.5292 +RilObject.prototype[REQUEST_ENTER_SIM_PIN2] = function REQUEST_ENTER_SIM_PIN2(length, options) {
  1.5293 +  this._processEnterAndChangeICCResponses(length, options);
  1.5294 +};
  1.5295 +RilObject.prototype[REQUEST_ENTER_SIM_PUK2] = function REQUEST_ENTER_SIM_PUK(length, options) {
  1.5296 +  this._processEnterAndChangeICCResponses(length, options);
  1.5297 +};
  1.5298 +RilObject.prototype[REQUEST_CHANGE_SIM_PIN] = function REQUEST_CHANGE_SIM_PIN(length, options) {
  1.5299 +  this._processEnterAndChangeICCResponses(length, options);
  1.5300 +};
  1.5301 +RilObject.prototype[REQUEST_CHANGE_SIM_PIN2] = function REQUEST_CHANGE_SIM_PIN2(length, options) {
  1.5302 +  this._processEnterAndChangeICCResponses(length, options);
  1.5303 +};
  1.5304 +RilObject.prototype[REQUEST_ENTER_NETWORK_DEPERSONALIZATION_CODE] =
  1.5305 +  function REQUEST_ENTER_NETWORK_DEPERSONALIZATION_CODE(length, options) {
  1.5306 +  this._processEnterAndChangeICCResponses(length, options);
  1.5307 +};
  1.5308 +RilObject.prototype[REQUEST_GET_CURRENT_CALLS] = function REQUEST_GET_CURRENT_CALLS(length, options) {
  1.5309 +  if (options.rilRequestError) {
  1.5310 +    return;
  1.5311 +  }
  1.5312 +
  1.5313 +  let Buf = this.context.Buf;
  1.5314 +  let calls_length = 0;
  1.5315 +  // The RIL won't even send us the length integer if there are no active calls.
  1.5316 +  // So only read this integer if the parcel actually has it.
  1.5317 +  if (length) {
  1.5318 +    calls_length = Buf.readInt32();
  1.5319 +  }
  1.5320 +  if (!calls_length) {
  1.5321 +    this._processCalls(null);
  1.5322 +    return;
  1.5323 +  }
  1.5324 +
  1.5325 +  let calls = {};
  1.5326 +  for (let i = 0; i < calls_length; i++) {
  1.5327 +    let call = {};
  1.5328 +
  1.5329 +    // Extra uint32 field to get correct callIndex and rest of call data for
  1.5330 +    // call waiting feature.
  1.5331 +    if (RILQUIRKS_EXTRA_UINT32_2ND_CALL && i > 0) {
  1.5332 +      Buf.readInt32();
  1.5333 +    }
  1.5334 +
  1.5335 +    call.state          = Buf.readInt32(); // CALL_STATE_*
  1.5336 +    call.callIndex      = Buf.readInt32(); // GSM index (1-based)
  1.5337 +    call.toa            = Buf.readInt32();
  1.5338 +    call.isMpty         = Boolean(Buf.readInt32());
  1.5339 +    call.isMT           = Boolean(Buf.readInt32());
  1.5340 +    call.als            = Buf.readInt32();
  1.5341 +    call.isVoice        = Boolean(Buf.readInt32());
  1.5342 +    call.isVoicePrivacy = Boolean(Buf.readInt32());
  1.5343 +    if (RILQUIRKS_CALLSTATE_EXTRA_UINT32) {
  1.5344 +      Buf.readInt32();
  1.5345 +    }
  1.5346 +    call.number             = Buf.readString(); //TODO munge with TOA
  1.5347 +    call.numberPresentation = Buf.readInt32(); // CALL_PRESENTATION_*
  1.5348 +    call.name               = Buf.readString();
  1.5349 +    call.namePresentation   = Buf.readInt32();
  1.5350 +
  1.5351 +    call.uusInfo = null;
  1.5352 +    let uusInfoPresent = Buf.readInt32();
  1.5353 +    if (uusInfoPresent == 1) {
  1.5354 +      call.uusInfo = {
  1.5355 +        type:     Buf.readInt32(),
  1.5356 +        dcs:      Buf.readInt32(),
  1.5357 +        userData: null //XXX TODO byte array?!?
  1.5358 +      };
  1.5359 +    }
  1.5360 +
  1.5361 +    calls[call.callIndex] = call;
  1.5362 +  }
  1.5363 +  this._processCalls(calls);
  1.5364 +};
  1.5365 +RilObject.prototype[REQUEST_DIAL] = function REQUEST_DIAL(length, options) {
  1.5366 +  // We already return a successful response before. Don't respond it again!
  1.5367 +  if (options.rilRequestError) {
  1.5368 +    this.getFailCauseCode((function(failCause) {
  1.5369 +      this._removePendingOutgoingCall(failCause);
  1.5370 +    }).bind(this));
  1.5371 +  }
  1.5372 +};
  1.5373 +RilObject.prototype[REQUEST_DIAL_EMERGENCY_CALL] = RilObject.prototype[REQUEST_DIAL];
  1.5374 +RilObject.prototype[REQUEST_GET_IMSI] = function REQUEST_GET_IMSI(length, options) {
  1.5375 +  if (options.rilRequestError) {
  1.5376 +    return;
  1.5377 +  }
  1.5378 +
  1.5379 +  this.iccInfoPrivate.imsi = this.context.Buf.readString();
  1.5380 +  if (DEBUG) {
  1.5381 +    this.context.debug("IMSI: " + this.iccInfoPrivate.imsi);
  1.5382 +  }
  1.5383 +
  1.5384 +  options.rilMessageType = "iccimsi";
  1.5385 +  options.imsi = this.iccInfoPrivate.imsi;
  1.5386 +  this.sendChromeMessage(options);
  1.5387 +};
  1.5388 +RilObject.prototype[REQUEST_HANGUP] = function REQUEST_HANGUP(length, options) {
  1.5389 +  if (options.rilRequestError) {
  1.5390 +    return;
  1.5391 +  }
  1.5392 +
  1.5393 +  this.getCurrentCalls();
  1.5394 +};
  1.5395 +RilObject.prototype[REQUEST_HANGUP_WAITING_OR_BACKGROUND] = function REQUEST_HANGUP_WAITING_OR_BACKGROUND(length, options) {
  1.5396 +  if (options.rilRequestError) {
  1.5397 +    return;
  1.5398 +  }
  1.5399 +
  1.5400 +  this.getCurrentCalls();
  1.5401 +};
  1.5402 +RilObject.prototype[REQUEST_HANGUP_FOREGROUND_RESUME_BACKGROUND] = function REQUEST_HANGUP_FOREGROUND_RESUME_BACKGROUND(length, options) {
  1.5403 +  if (options.rilRequestError) {
  1.5404 +    return;
  1.5405 +  }
  1.5406 +
  1.5407 +  this.getCurrentCalls();
  1.5408 +};
  1.5409 +RilObject.prototype[REQUEST_SWITCH_WAITING_OR_HOLDING_AND_ACTIVE] = function REQUEST_SWITCH_WAITING_OR_HOLDING_AND_ACTIVE(length, options) {
  1.5410 +  options.success = (options.rilRequestError === 0);
  1.5411 +  if (!options.success) {
  1.5412 +    options.errorMsg = RIL_ERROR_TO_GECKO_ERROR[options.rilRequestError];
  1.5413 +    this.sendChromeMessage(options);
  1.5414 +    return;
  1.5415 +  }
  1.5416 +
  1.5417 +  this.sendChromeMessage(options);
  1.5418 +  this.getCurrentCalls();
  1.5419 +};
  1.5420 +RilObject.prototype[REQUEST_CONFERENCE] = function REQUEST_CONFERENCE(length, options) {
  1.5421 +  options.success = (options.rilRequestError === 0);
  1.5422 +  if (!options.success) {
  1.5423 +    this._hasConferenceRequest = false;
  1.5424 +    options.errorName = "addError";
  1.5425 +    options.errorMsg = RIL_ERROR_TO_GECKO_ERROR[options.rilRequestError];
  1.5426 +    this.sendChromeMessage(options);
  1.5427 +    return;
  1.5428 +  }
  1.5429 +
  1.5430 +  this.sendChromeMessage(options);
  1.5431 +};
  1.5432 +RilObject.prototype[REQUEST_UDUB] = null;
  1.5433 +RilObject.prototype[REQUEST_LAST_CALL_FAIL_CAUSE] = function REQUEST_LAST_CALL_FAIL_CAUSE(length, options) {
  1.5434 +  let Buf = this.context.Buf;
  1.5435 +  let num = length ? Buf.readInt32() : 0;
  1.5436 +  let failCause = num ? RIL_CALL_FAILCAUSE_TO_GECKO_CALL_ERROR[Buf.readInt32()] : null;
  1.5437 +  if (options.callback) {
  1.5438 +    options.callback(failCause);
  1.5439 +  }
  1.5440 +};
  1.5441 +RilObject.prototype[REQUEST_SIGNAL_STRENGTH] = function REQUEST_SIGNAL_STRENGTH(length, options) {
  1.5442 +  this._receivedNetworkInfo(NETWORK_INFO_SIGNAL);
  1.5443 +
  1.5444 +  if (options.rilRequestError) {
  1.5445 +    return;
  1.5446 +  }
  1.5447 +
  1.5448 +  let Buf = this.context.Buf;
  1.5449 +  let signal = {
  1.5450 +    gsmSignalStrength: Buf.readInt32(),
  1.5451 +    gsmBitErrorRate:   Buf.readInt32(),
  1.5452 +    cdmaDBM:           Buf.readInt32(),
  1.5453 +    cdmaECIO:          Buf.readInt32(),
  1.5454 +    evdoDBM:           Buf.readInt32(),
  1.5455 +    evdoECIO:          Buf.readInt32(),
  1.5456 +    evdoSNR:           Buf.readInt32()
  1.5457 +  };
  1.5458 +
  1.5459 +  if (!this.v5Legacy) {
  1.5460 +    signal.lteSignalStrength = Buf.readInt32();
  1.5461 +    signal.lteRSRP =           Buf.readInt32();
  1.5462 +    signal.lteRSRQ =           Buf.readInt32();
  1.5463 +    signal.lteRSSNR =          Buf.readInt32();
  1.5464 +    signal.lteCQI =            Buf.readInt32();
  1.5465 +  }
  1.5466 +
  1.5467 +  if (DEBUG) this.context.debug("signal strength: " + JSON.stringify(signal));
  1.5468 +
  1.5469 +  this._processSignalStrength(signal);
  1.5470 +};
  1.5471 +RilObject.prototype[REQUEST_VOICE_REGISTRATION_STATE] = function REQUEST_VOICE_REGISTRATION_STATE(length, options) {
  1.5472 +  this._receivedNetworkInfo(NETWORK_INFO_VOICE_REGISTRATION_STATE);
  1.5473 +
  1.5474 +  if (options.rilRequestError) {
  1.5475 +    return;
  1.5476 +  }
  1.5477 +
  1.5478 +  let state = this.context.Buf.readStringList();
  1.5479 +  if (DEBUG) this.context.debug("voice registration state: " + state);
  1.5480 +
  1.5481 +  this._processVoiceRegistrationState(state);
  1.5482 +
  1.5483 +  if (this.cachedDialRequest &&
  1.5484 +       (this.voiceRegistrationState.emergencyCallsOnly ||
  1.5485 +        this.voiceRegistrationState.connected) &&
  1.5486 +      this.voiceRegistrationState.radioTech != NETWORK_CREG_TECH_UNKNOWN) {
  1.5487 +    // Radio is ready for making the cached emergency call.
  1.5488 +    this.cachedDialRequest.callback();
  1.5489 +    this.cachedDialRequest = null;
  1.5490 +  }
  1.5491 +};
  1.5492 +RilObject.prototype[REQUEST_DATA_REGISTRATION_STATE] = function REQUEST_DATA_REGISTRATION_STATE(length, options) {
  1.5493 +  this._receivedNetworkInfo(NETWORK_INFO_DATA_REGISTRATION_STATE);
  1.5494 +
  1.5495 +  if (options.rilRequestError) {
  1.5496 +    return;
  1.5497 +  }
  1.5498 +
  1.5499 +  let state = this.context.Buf.readStringList();
  1.5500 +  this._processDataRegistrationState(state);
  1.5501 +};
  1.5502 +RilObject.prototype[REQUEST_OPERATOR] = function REQUEST_OPERATOR(length, options) {
  1.5503 +  this._receivedNetworkInfo(NETWORK_INFO_OPERATOR);
  1.5504 +
  1.5505 +  if (options.rilRequestError) {
  1.5506 +    return;
  1.5507 +  }
  1.5508 +
  1.5509 +  let operatorData = this.context.Buf.readStringList();
  1.5510 +  if (DEBUG) this.context.debug("Operator: " + operatorData);
  1.5511 +  this._processOperator(operatorData);
  1.5512 +};
  1.5513 +RilObject.prototype[REQUEST_RADIO_POWER] = function REQUEST_RADIO_POWER(length, options) {
  1.5514 +  if (options.rilMessageType == null) {
  1.5515 +    // The request was made by ril_worker itself.
  1.5516 +    if (options.rilRequestError) {
  1.5517 +      if (this.cachedDialRequest && options.enabled) {
  1.5518 +        // Turning on radio fails. Notify the error of making an emergency call.
  1.5519 +        this.cachedDialRequest.onerror(GECKO_ERROR_RADIO_NOT_AVAILABLE);
  1.5520 +        this.cachedDialRequest = null;
  1.5521 +      }
  1.5522 +    }
  1.5523 +    return;
  1.5524 +  }
  1.5525 +
  1.5526 +  options.errorMsg = RIL_ERROR_TO_GECKO_ERROR[options.rilRequestError];
  1.5527 +  this.sendChromeMessage(options);
  1.5528 +};
  1.5529 +RilObject.prototype[REQUEST_DTMF] = null;
  1.5530 +RilObject.prototype[REQUEST_SEND_SMS] = function REQUEST_SEND_SMS(length, options) {
  1.5531 +  this._processSmsSendResult(length, options);
  1.5532 +};
  1.5533 +RilObject.prototype[REQUEST_SEND_SMS_EXPECT_MORE] = null;
  1.5534 +
  1.5535 +RilObject.prototype.readSetupDataCall_v5 = function readSetupDataCall_v5(options) {
  1.5536 +  if (!options) {
  1.5537 +    options = {};
  1.5538 +  }
  1.5539 +  let [cid, ifname, addresses, dnses, gateways] = this.context.Buf.readStringList();
  1.5540 +  options.cid = cid;
  1.5541 +  options.ifname = ifname;
  1.5542 +  options.addresses = addresses ? [addresses] : [];
  1.5543 +  options.dnses = dnses ? [dnses] : [];
  1.5544 +  options.gateways = gateways ? [gateways] : [];
  1.5545 +  options.active = DATACALL_ACTIVE_UNKNOWN;
  1.5546 +  options.state = GECKO_NETWORK_STATE_CONNECTING;
  1.5547 +  return options;
  1.5548 +};
  1.5549 +
  1.5550 +RilObject.prototype[REQUEST_SETUP_DATA_CALL] = function REQUEST_SETUP_DATA_CALL(length, options) {
  1.5551 +  if (options.rilRequestError) {
  1.5552 +    // On Data Call generic errors, we shall notify caller
  1.5553 +    this._sendDataCallError(options, options.rilRequestError);
  1.5554 +    return;
  1.5555 +  }
  1.5556 +
  1.5557 +  if (this.v5Legacy) {
  1.5558 +    // Populate the `options` object with the data call information. That way
  1.5559 +    // we retain the APN and other info about how the data call was set up.
  1.5560 +    this.readSetupDataCall_v5(options);
  1.5561 +    this.currentDataCalls[options.cid] = options;
  1.5562 +    options.rilMessageType = "datacallstatechange";
  1.5563 +    this.sendChromeMessage(options);
  1.5564 +    // Let's get the list of data calls to ensure we know whether it's active
  1.5565 +    // or not.
  1.5566 +    this.getDataCallList();
  1.5567 +    return;
  1.5568 +  }
  1.5569 +  // Pass `options` along. That way we retain the APN and other info about
  1.5570 +  // how the data call was set up.
  1.5571 +  this[REQUEST_DATA_CALL_LIST](length, options);
  1.5572 +};
  1.5573 +RilObject.prototype[REQUEST_SIM_IO] = function REQUEST_SIM_IO(length, options) {
  1.5574 +  let ICCIOHelper = this.context.ICCIOHelper;
  1.5575 +  if (!length) {
  1.5576 +    ICCIOHelper.processICCIOError(options);
  1.5577 +    return;
  1.5578 +  }
  1.5579 +
  1.5580 +  // Don't need to read rilRequestError since we can know error status from
  1.5581 +  // sw1 and sw2.
  1.5582 +  let Buf = this.context.Buf;
  1.5583 +  options.sw1 = Buf.readInt32();
  1.5584 +  options.sw2 = Buf.readInt32();
  1.5585 +  if (options.sw1 != ICC_STATUS_NORMAL_ENDING) {
  1.5586 +    ICCIOHelper.processICCIOError(options);
  1.5587 +    return;
  1.5588 +  }
  1.5589 +  ICCIOHelper.processICCIO(options);
  1.5590 +};
  1.5591 +RilObject.prototype[REQUEST_SEND_USSD] = function REQUEST_SEND_USSD(length, options) {
  1.5592 +  if (DEBUG) {
  1.5593 +    this.context.debug("REQUEST_SEND_USSD " + JSON.stringify(options));
  1.5594 +  }
  1.5595 +  options.success = (this._ussdSession = options.rilRequestError === 0);
  1.5596 +  options.errorMsg = RIL_ERROR_TO_GECKO_ERROR[options.rilRequestError];
  1.5597 +  this.sendChromeMessage(options);
  1.5598 +};
  1.5599 +RilObject.prototype[REQUEST_CANCEL_USSD] = function REQUEST_CANCEL_USSD(length, options) {
  1.5600 +  if (DEBUG) {
  1.5601 +    this.context.debug("REQUEST_CANCEL_USSD" + JSON.stringify(options));
  1.5602 +  }
  1.5603 +  options.success = (options.rilRequestError === 0);
  1.5604 +  this._ussdSession = !options.success;
  1.5605 +  options.errorMsg = RIL_ERROR_TO_GECKO_ERROR[options.rilRequestError];
  1.5606 +  this.sendChromeMessage(options);
  1.5607 +};
  1.5608 +RilObject.prototype[REQUEST_GET_CLIR] = function REQUEST_GET_CLIR(length, options) {
  1.5609 +  options.success = (options.rilRequestError === 0);
  1.5610 +  if (!options.success) {
  1.5611 +    options.errorMsg = RIL_ERROR_TO_GECKO_ERROR[options.rilRequestError];
  1.5612 +    this.sendChromeMessage(options);
  1.5613 +    return;
  1.5614 +  }
  1.5615 +
  1.5616 +  let Buf = this.context.Buf;
  1.5617 +  let bufLength = Buf.readInt32();
  1.5618 +  if (!bufLength || bufLength < 2) {
  1.5619 +    options.success = false;
  1.5620 +    options.errorMsg = GECKO_ERROR_GENERIC_FAILURE;
  1.5621 +    this.sendChromeMessage(options);
  1.5622 +    return;
  1.5623 +  }
  1.5624 +
  1.5625 +  options.n = Buf.readInt32(); // Will be TS 27.007 +CLIR parameter 'n'.
  1.5626 +  options.m = Buf.readInt32(); // Will be TS 27.007 +CLIR parameter 'm'.
  1.5627 +
  1.5628 +  if (options.rilMessageType === "sendMMI") {
  1.5629 +    // TS 27.007 +CLIR parameter 'm'.
  1.5630 +    switch (options.m) {
  1.5631 +      // CLIR not provisioned.
  1.5632 +      case 0:
  1.5633 +        options.statusMessage = MMI_SM_KS_SERVICE_NOT_PROVISIONED;
  1.5634 +        break;
  1.5635 +      // CLIR provisioned in permanent mode.
  1.5636 +      case 1:
  1.5637 +        options.statusMessage = MMI_SM_KS_CLIR_PERMANENT;
  1.5638 +        break;
  1.5639 +      // Unknown (e.g. no network, etc.).
  1.5640 +      case 2:
  1.5641 +        options.success = false;
  1.5642 +        options.errorMsg = MMI_ERROR_KS_ERROR;
  1.5643 +        break;
  1.5644 +      // CLIR temporary mode presentation restricted.
  1.5645 +      case 3:
  1.5646 +        // TS 27.007 +CLIR parameter 'n'.
  1.5647 +        switch (options.n) {
  1.5648 +          // Default.
  1.5649 +          case 0:
  1.5650 +          // CLIR invocation.
  1.5651 +          case 1:
  1.5652 +            options.statusMessage = MMI_SM_KS_CLIR_DEFAULT_ON_NEXT_CALL_ON;
  1.5653 +            break;
  1.5654 +          // CLIR suppression.
  1.5655 +          case 2:
  1.5656 +            options.statusMessage = MMI_SM_KS_CLIR_DEFAULT_ON_NEXT_CALL_OFF;
  1.5657 +            break;
  1.5658 +          default:
  1.5659 +            options.success = false;
  1.5660 +            options.errorMsg = GECKO_ERROR_GENERIC_FAILURE;
  1.5661 +            break;
  1.5662 +        }
  1.5663 +        break;
  1.5664 +      // CLIR temporary mode presentation allowed.
  1.5665 +      case 4:
  1.5666 +        // TS 27.007 +CLIR parameter 'n'.
  1.5667 +        switch (options.n) {
  1.5668 +          // Default.
  1.5669 +          case 0:
  1.5670 +          // CLIR suppression.
  1.5671 +          case 2:
  1.5672 +            options.statusMessage = MMI_SM_KS_CLIR_DEFAULT_OFF_NEXT_CALL_OFF;
  1.5673 +            break;
  1.5674 +          // CLIR invocation.
  1.5675 +          case 1:
  1.5676 +            options.statusMessage = MMI_SM_KS_CLIR_DEFAULT_OFF_NEXT_CALL_ON;
  1.5677 +            break;
  1.5678 +          default:
  1.5679 +            options.success = false;
  1.5680 +            options.errorMsg = GECKO_ERROR_GENERIC_FAILURE;
  1.5681 +            break;
  1.5682 +        }
  1.5683 +        break;
  1.5684 +      default:
  1.5685 +        options.success = false;
  1.5686 +        options.errorMsg = GECKO_ERROR_GENERIC_FAILURE;
  1.5687 +        break;
  1.5688 +    }
  1.5689 +  }
  1.5690 +
  1.5691 +  this.sendChromeMessage(options);
  1.5692 +};
  1.5693 +RilObject.prototype[REQUEST_SET_CLIR] = function REQUEST_SET_CLIR(length, options) {
  1.5694 +  if (options.rilMessageType == null) {
  1.5695 +    // The request was made by ril_worker itself automatically. Don't report.
  1.5696 +    return;
  1.5697 +  }
  1.5698 +  options.success = (options.rilRequestError === 0);
  1.5699 +  if (!options.success) {
  1.5700 +    options.errorMsg = RIL_ERROR_TO_GECKO_ERROR[options.rilRequestError];
  1.5701 +  } else if (options.rilMessageType === "sendMMI") {
  1.5702 +    switch (options.procedure) {
  1.5703 +      case MMI_PROCEDURE_ACTIVATION:
  1.5704 +        options.statusMessage = MMI_SM_KS_SERVICE_ENABLED;
  1.5705 +        break;
  1.5706 +      case MMI_PROCEDURE_DEACTIVATION:
  1.5707 +        options.statusMessage = MMI_SM_KS_SERVICE_DISABLED;
  1.5708 +        break;
  1.5709 +    }
  1.5710 +  }
  1.5711 +  this.sendChromeMessage(options);
  1.5712 +};
  1.5713 +
  1.5714 +RilObject.prototype[REQUEST_QUERY_CALL_FORWARD_STATUS] =
  1.5715 +  function REQUEST_QUERY_CALL_FORWARD_STATUS(length, options) {
  1.5716 +  options.success = (options.rilRequestError === 0);
  1.5717 +  if (!options.success) {
  1.5718 +    options.errorMsg = RIL_ERROR_TO_GECKO_ERROR[options.rilRequestError];
  1.5719 +    this.sendChromeMessage(options);
  1.5720 +    return;
  1.5721 +  }
  1.5722 +
  1.5723 +  let Buf = this.context.Buf;
  1.5724 +  let rulesLength = 0;
  1.5725 +  if (length) {
  1.5726 +    rulesLength = Buf.readInt32();
  1.5727 +  }
  1.5728 +  if (!rulesLength) {
  1.5729 +    options.success = false;
  1.5730 +    options.errorMsg = GECKO_ERROR_GENERIC_FAILURE;
  1.5731 +    this.sendChromeMessage(options);
  1.5732 +    return;
  1.5733 +  }
  1.5734 +  let rules = new Array(rulesLength);
  1.5735 +  for (let i = 0; i < rulesLength; i++) {
  1.5736 +    let rule = {};
  1.5737 +    rule.active       = Buf.readInt32() == 1; // CALL_FORWARD_STATUS_*
  1.5738 +    rule.reason       = Buf.readInt32(); // CALL_FORWARD_REASON_*
  1.5739 +    rule.serviceClass = Buf.readInt32();
  1.5740 +    rule.toa          = Buf.readInt32();
  1.5741 +    rule.number       = Buf.readString();
  1.5742 +    rule.timeSeconds  = Buf.readInt32();
  1.5743 +    rules[i] = rule;
  1.5744 +  }
  1.5745 +  options.rules = rules;
  1.5746 +  if (options.rilMessageType === "sendMMI") {
  1.5747 +    options.statusMessage = MMI_SM_KS_SERVICE_INTERROGATED;
  1.5748 +    // MMI query call forwarding options request returns a set of rules that
  1.5749 +    // will be exposed in the form of an array of nsIDOMMozMobileCFInfo
  1.5750 +    // instances.
  1.5751 +    options.additionalInformation = rules;
  1.5752 +  }
  1.5753 +  this.sendChromeMessage(options);
  1.5754 +};
  1.5755 +RilObject.prototype[REQUEST_SET_CALL_FORWARD] =
  1.5756 +  function REQUEST_SET_CALL_FORWARD(length, options) {
  1.5757 +  options.success = (options.rilRequestError === 0);
  1.5758 +  if (!options.success) {
  1.5759 +    options.errorMsg = RIL_ERROR_TO_GECKO_ERROR[options.rilRequestError];
  1.5760 +  } else if (options.rilMessageType === "sendMMI") {
  1.5761 +    switch (options.action) {
  1.5762 +      case CALL_FORWARD_ACTION_ENABLE:
  1.5763 +        options.statusMessage = MMI_SM_KS_SERVICE_ENABLED;
  1.5764 +        break;
  1.5765 +      case CALL_FORWARD_ACTION_DISABLE:
  1.5766 +        options.statusMessage = MMI_SM_KS_SERVICE_DISABLED;
  1.5767 +        break;
  1.5768 +      case CALL_FORWARD_ACTION_REGISTRATION:
  1.5769 +        options.statusMessage = MMI_SM_KS_SERVICE_REGISTERED;
  1.5770 +        break;
  1.5771 +      case CALL_FORWARD_ACTION_ERASURE:
  1.5772 +        options.statusMessage = MMI_SM_KS_SERVICE_ERASED;
  1.5773 +        break;
  1.5774 +    }
  1.5775 +  }
  1.5776 +  this.sendChromeMessage(options);
  1.5777 +};
  1.5778 +RilObject.prototype[REQUEST_QUERY_CALL_WAITING] =
  1.5779 +  function REQUEST_QUERY_CALL_WAITING(length, options) {
  1.5780 +  options.success = (options.rilRequestError === 0);
  1.5781 +  if (!options.success) {
  1.5782 +    options.errorMsg = RIL_ERROR_TO_GECKO_ERROR[options.rilRequestError];
  1.5783 +    this.sendChromeMessage(options);
  1.5784 +    return;
  1.5785 +  }
  1.5786 +
  1.5787 +  if (options.callback) {
  1.5788 +    options.callback.call(this, options);
  1.5789 +    return;
  1.5790 +  }
  1.5791 +
  1.5792 +  let Buf = this.context.Buf;
  1.5793 +  options.length = Buf.readInt32();
  1.5794 +  options.enabled = ((Buf.readInt32() == 1) &&
  1.5795 +                     ((Buf.readInt32() & ICC_SERVICE_CLASS_VOICE) == 0x01));
  1.5796 +  this.sendChromeMessage(options);
  1.5797 +};
  1.5798 +
  1.5799 +RilObject.prototype[REQUEST_SET_CALL_WAITING] = function REQUEST_SET_CALL_WAITING(length, options) {
  1.5800 +  options.success = (options.rilRequestError === 0);
  1.5801 +  if (!options.success) {
  1.5802 +    options.errorMsg = RIL_ERROR_TO_GECKO_ERROR[options.rilRequestError];
  1.5803 +    this.sendChromeMessage(options);
  1.5804 +    return;
  1.5805 +  }
  1.5806 +
  1.5807 +  if (options.callback) {
  1.5808 +    options.callback.call(this, options);
  1.5809 +    return;
  1.5810 +  }
  1.5811 +
  1.5812 +  this.sendChromeMessage(options);
  1.5813 +};
  1.5814 +RilObject.prototype[REQUEST_SMS_ACKNOWLEDGE] = null;
  1.5815 +RilObject.prototype[REQUEST_GET_IMEI] = function REQUEST_GET_IMEI(length, options) {
  1.5816 +  this.IMEI = this.context.Buf.readString();
  1.5817 +  let rilMessageType = options.rilMessageType;
  1.5818 +  // So far we only send the IMEI back to chrome if it was requested via MMI.
  1.5819 +  if (rilMessageType !== "sendMMI") {
  1.5820 +    return;
  1.5821 +  }
  1.5822 +
  1.5823 +  options.success = (options.rilRequestError === 0);
  1.5824 +  options.errorMsg = RIL_ERROR_TO_GECKO_ERROR[options.rilRequestError];
  1.5825 +  if ((!options.success || this.IMEI == null) && !options.errorMsg) {
  1.5826 +    options.errorMsg = GECKO_ERROR_GENERIC_FAILURE;
  1.5827 +  }
  1.5828 +  options.statusMessage = this.IMEI;
  1.5829 +  this.sendChromeMessage(options);
  1.5830 +};
  1.5831 +RilObject.prototype[REQUEST_GET_IMEISV] = function REQUEST_GET_IMEISV(length, options) {
  1.5832 +  if (options.rilRequestError) {
  1.5833 +    return;
  1.5834 +  }
  1.5835 +
  1.5836 +  this.IMEISV = this.context.Buf.readString();
  1.5837 +};
  1.5838 +RilObject.prototype[REQUEST_ANSWER] = null;
  1.5839 +RilObject.prototype[REQUEST_DEACTIVATE_DATA_CALL] = function REQUEST_DEACTIVATE_DATA_CALL(length, options) {
  1.5840 +  if (options.rilRequestError) {
  1.5841 +    return;
  1.5842 +  }
  1.5843 +
  1.5844 +  let datacall = this.currentDataCalls[options.cid];
  1.5845 +  delete this.currentDataCalls[options.cid];
  1.5846 +  datacall.state = GECKO_NETWORK_STATE_UNKNOWN;
  1.5847 +  datacall.rilMessageType = "datacallstatechange";
  1.5848 +  this.sendChromeMessage(datacall);
  1.5849 +};
  1.5850 +RilObject.prototype[REQUEST_QUERY_FACILITY_LOCK] = function REQUEST_QUERY_FACILITY_LOCK(length, options) {
  1.5851 +  options.success = (options.rilRequestError === 0);
  1.5852 +  if (!options.success) {
  1.5853 +    options.errorMsg = RIL_ERROR_TO_GECKO_ERROR[options.rilRequestError];
  1.5854 +  }
  1.5855 +
  1.5856 +  let services;
  1.5857 +  if (length) {
  1.5858 +    // Buf.readInt32List()[0] for Call Barring is a bit vector of services.
  1.5859 +    services = this.context.Buf.readInt32List()[0];
  1.5860 +  } else {
  1.5861 +    options.success = false;
  1.5862 +    options.errorMsg = GECKO_ERROR_GENERIC_FAILURE;
  1.5863 +    this.sendChromeMessage(options);
  1.5864 +    return;
  1.5865 +  }
  1.5866 +
  1.5867 +  options.enabled = services === 0 ? false : true;
  1.5868 +
  1.5869 +  if (options.success && (options.rilMessageType === "sendMMI")) {
  1.5870 +    if (!options.enabled) {
  1.5871 +      options.statusMessage = MMI_SM_KS_SERVICE_DISABLED;
  1.5872 +    } else {
  1.5873 +      options.statusMessage = MMI_SM_KS_SERVICE_ENABLED_FOR;
  1.5874 +      let serviceClass = [];
  1.5875 +      for (let serviceClassMask = 1;
  1.5876 +           serviceClassMask <= ICC_SERVICE_CLASS_MAX;
  1.5877 +           serviceClassMask <<= 1) {
  1.5878 +        if ((serviceClassMask & services) !== 0) {
  1.5879 +          serviceClass.push(MMI_KS_SERVICE_CLASS_MAPPING[serviceClassMask]);
  1.5880 +        }
  1.5881 +      }
  1.5882 +
  1.5883 +      options.additionalInformation = serviceClass;
  1.5884 +    }
  1.5885 +  }
  1.5886 +  this.sendChromeMessage(options);
  1.5887 +};
  1.5888 +RilObject.prototype[REQUEST_SET_FACILITY_LOCK] = function REQUEST_SET_FACILITY_LOCK(length, options) {
  1.5889 +  options.success = (options.rilRequestError === 0);
  1.5890 +  if (!options.success) {
  1.5891 +    options.errorMsg = RIL_ERROR_TO_GECKO_ERROR[options.rilRequestError];
  1.5892 +  }
  1.5893 +
  1.5894 +  options.retryCount = length ? this.context.Buf.readInt32List()[0] : -1;
  1.5895 +
  1.5896 +  if (options.success && (options.rilMessageType === "sendMMI")) {
  1.5897 +    switch (options.procedure) {
  1.5898 +      case MMI_PROCEDURE_ACTIVATION:
  1.5899 +        options.statusMessage = MMI_SM_KS_SERVICE_ENABLED;
  1.5900 +        break;
  1.5901 +      case MMI_PROCEDURE_DEACTIVATION:
  1.5902 +        options.statusMessage = MMI_SM_KS_SERVICE_DISABLED;
  1.5903 +        break;
  1.5904 +    }
  1.5905 +  }
  1.5906 +  this.sendChromeMessage(options);
  1.5907 +};
  1.5908 +RilObject.prototype[REQUEST_CHANGE_BARRING_PASSWORD] =
  1.5909 +  function REQUEST_CHANGE_BARRING_PASSWORD(length, options) {
  1.5910 +  if (options.rilRequestError) {
  1.5911 +    options.errorMsg = RIL_ERROR_TO_GECKO_ERROR[options.rilRequestError];
  1.5912 +  }
  1.5913 +  this.sendChromeMessage(options);
  1.5914 +};
  1.5915 +RilObject.prototype[REQUEST_SIM_OPEN_CHANNEL] = function REQUEST_SIM_OPEN_CHANNEL(length, options) {
  1.5916 +  if (options.rilRequestError) {
  1.5917 +    options.errorMsg = RIL_ERROR_TO_GECKO_ERROR[options.rilRequestError];
  1.5918 +    this.sendChromeMessage(options);
  1.5919 +    return;
  1.5920 +  }
  1.5921 +
  1.5922 +  options.channel = this.context.Buf.readInt32();
  1.5923 +  if (DEBUG) {
  1.5924 +    this.context.debug("Setting channel number in options: " + options.channel);
  1.5925 +  }
  1.5926 +  this.sendChromeMessage(options);
  1.5927 +};
  1.5928 +RilObject.prototype[REQUEST_SIM_CLOSE_CHANNEL] = function REQUEST_SIM_CLOSE_CHANNEL(length, options) {
  1.5929 +  if (options.rilRequestError) {
  1.5930 +    options.error = RIL_ERROR_TO_GECKO_ERROR[options.rilRequestError];
  1.5931 +    this.sendChromeMessage(options);
  1.5932 +    return;
  1.5933 +  }
  1.5934 +
  1.5935 +  // No return value
  1.5936 +  this.sendChromeMessage(options);
  1.5937 +};
  1.5938 +RilObject.prototype[REQUEST_SIM_ACCESS_CHANNEL] = function REQUEST_SIM_ACCESS_CHANNEL(length, options) {
  1.5939 +  if (options.rilRequestError) {
  1.5940 +    options.error = RIL_ERROR_TO_GECKO_ERROR[options.rilRequestError];
  1.5941 +    this.sendChromeMessage(options);
  1.5942 +  }
  1.5943 +
  1.5944 +  let Buf = this.context.Buf;
  1.5945 +  options.sw1 = Buf.readInt32();
  1.5946 +  options.sw2 = Buf.readInt32();
  1.5947 +  options.simResponse = Buf.readString();
  1.5948 +  if (DEBUG) {
  1.5949 +    this.context.debug("Setting return values for RIL[REQUEST_SIM_ACCESS_CHANNEL]: [" +
  1.5950 +                       options.sw1 + "," +
  1.5951 +                       options.sw2 + ", " +
  1.5952 +                       options.simResponse + "]");
  1.5953 +  }
  1.5954 +  this.sendChromeMessage(options);
  1.5955 +};
  1.5956 +RilObject.prototype[REQUEST_QUERY_NETWORK_SELECTION_MODE] = function REQUEST_QUERY_NETWORK_SELECTION_MODE(length, options) {
  1.5957 +  this._receivedNetworkInfo(NETWORK_INFO_NETWORK_SELECTION_MODE);
  1.5958 +
  1.5959 +  if (options.rilRequestError) {
  1.5960 +    return;
  1.5961 +  }
  1.5962 +
  1.5963 +  let mode = this.context.Buf.readInt32List();
  1.5964 +  let selectionMode;
  1.5965 +
  1.5966 +  switch (mode[0]) {
  1.5967 +    case NETWORK_SELECTION_MODE_AUTOMATIC:
  1.5968 +      selectionMode = GECKO_NETWORK_SELECTION_AUTOMATIC;
  1.5969 +      break;
  1.5970 +    case NETWORK_SELECTION_MODE_MANUAL:
  1.5971 +      selectionMode = GECKO_NETWORK_SELECTION_MANUAL;
  1.5972 +      break;
  1.5973 +    default:
  1.5974 +      selectionMode = GECKO_NETWORK_SELECTION_UNKNOWN;
  1.5975 +      break;
  1.5976 +  }
  1.5977 +
  1.5978 +  if (this.networkSelectionMode != selectionMode) {
  1.5979 +    this.networkSelectionMode = options.mode = selectionMode;
  1.5980 +    options.rilMessageType = "networkselectionmodechange";
  1.5981 +    this._sendNetworkInfoMessage(NETWORK_INFO_NETWORK_SELECTION_MODE, options);
  1.5982 +  }
  1.5983 +};
  1.5984 +RilObject.prototype[REQUEST_SET_NETWORK_SELECTION_AUTOMATIC] = function REQUEST_SET_NETWORK_SELECTION_AUTOMATIC(length, options) {
  1.5985 +  if (options.rilRequestError) {
  1.5986 +    options.errorMsg = RIL_ERROR_TO_GECKO_ERROR[options.rilRequestError];
  1.5987 +  }
  1.5988 +
  1.5989 +  this.sendChromeMessage(options);
  1.5990 +};
  1.5991 +RilObject.prototype[REQUEST_SET_NETWORK_SELECTION_MANUAL] = function REQUEST_SET_NETWORK_SELECTION_MANUAL(length, options) {
  1.5992 +  if (options.rilRequestError) {
  1.5993 +    options.errorMsg = RIL_ERROR_TO_GECKO_ERROR[options.rilRequestError];
  1.5994 +  }
  1.5995 +
  1.5996 +  this.sendChromeMessage(options);
  1.5997 +};
  1.5998 +RilObject.prototype[REQUEST_QUERY_AVAILABLE_NETWORKS] = function REQUEST_QUERY_AVAILABLE_NETWORKS(length, options) {
  1.5999 +  if (options.rilRequestError) {
  1.6000 +    options.errorMsg = RIL_ERROR_TO_GECKO_ERROR[options.rilRequestError];
  1.6001 +  } else {
  1.6002 +    options.networks = this._processNetworks();
  1.6003 +  }
  1.6004 +  this.sendChromeMessage(options);
  1.6005 +};
  1.6006 +RilObject.prototype[REQUEST_DTMF_START] = null;
  1.6007 +RilObject.prototype[REQUEST_DTMF_STOP] = null;
  1.6008 +RilObject.prototype[REQUEST_BASEBAND_VERSION] = function REQUEST_BASEBAND_VERSION(length, options) {
  1.6009 +  if (options.rilRequestError) {
  1.6010 +    return;
  1.6011 +  }
  1.6012 +
  1.6013 +  this.basebandVersion = this.context.Buf.readString();
  1.6014 +  if (DEBUG) this.context.debug("Baseband version: " + this.basebandVersion);
  1.6015 +};
  1.6016 +RilObject.prototype[REQUEST_SEPARATE_CONNECTION] = function REQUEST_SEPARATE_CONNECTION(length, options) {
  1.6017 +  options.success = (options.rilRequestError === 0);
  1.6018 +  if (!options.success) {
  1.6019 +    options.errorName = "removeError";
  1.6020 +    options.errorMsg = RIL_ERROR_TO_GECKO_ERROR[options.rilRequestError];
  1.6021 +    this.sendChromeMessage(options);
  1.6022 +    return;
  1.6023 +  }
  1.6024 +
  1.6025 +  this.sendChromeMessage(options);
  1.6026 +};
  1.6027 +RilObject.prototype[REQUEST_SET_MUTE] = null;
  1.6028 +RilObject.prototype[REQUEST_GET_MUTE] = null;
  1.6029 +RilObject.prototype[REQUEST_QUERY_CLIP] = function REQUEST_QUERY_CLIP(length, options) {
  1.6030 +  options.success = (options.rilRequestError === 0);
  1.6031 +  if (!options.success) {
  1.6032 +    options.errorMsg = RIL_ERROR_TO_GECKO_ERROR[options.rilRequestError];
  1.6033 +    this.sendChromeMessage(options);
  1.6034 +    return;
  1.6035 +  }
  1.6036 +
  1.6037 +  let Buf = this.context.Buf;
  1.6038 +  let bufLength = Buf.readInt32();
  1.6039 +  if (!bufLength) {
  1.6040 +    options.success = false;
  1.6041 +    options.errorMsg = GECKO_ERROR_GENERIC_FAILURE;
  1.6042 +    this.sendChromeMessage(options);
  1.6043 +    return;
  1.6044 +  }
  1.6045 +
  1.6046 +  // options.provisioned informs about the called party receives the calling
  1.6047 +  // party's address information:
  1.6048 +  // 0 for CLIP not provisioned
  1.6049 +  // 1 for CLIP provisioned
  1.6050 +  // 2 for unknown
  1.6051 +  options.provisioned = Buf.readInt32();
  1.6052 +  if (options.rilMessageType === "sendMMI") {
  1.6053 +    switch (options.provisioned) {
  1.6054 +      case 0:
  1.6055 +        options.statusMessage = MMI_SM_KS_SERVICE_DISABLED;
  1.6056 +        break;
  1.6057 +      case 1:
  1.6058 +        options.statusMessage = MMI_SM_KS_SERVICE_ENABLED;
  1.6059 +        break;
  1.6060 +      default:
  1.6061 +        options.success = false;
  1.6062 +        options.errorMsg = MMI_ERROR_KS_ERROR;
  1.6063 +        break;
  1.6064 +    }
  1.6065 +  }
  1.6066 +  this.sendChromeMessage(options);
  1.6067 +};
  1.6068 +RilObject.prototype[REQUEST_LAST_DATA_CALL_FAIL_CAUSE] = null;
  1.6069 +
  1.6070 +/**
  1.6071 + * V3:
  1.6072 + *  # address   - A space-delimited list of addresses.
  1.6073 + *
  1.6074 + * V4:
  1.6075 + *  # address   - An address.
  1.6076 + *
  1.6077 + * V5:
  1.6078 + *  # addresses - A space-delimited list of addresses.
  1.6079 + *  # dnses     - A space-delimited list of DNS server addresses.
  1.6080 + *
  1.6081 + * V6:
  1.6082 + *  # addresses - A space-delimited list of addresses with optional "/" prefix
  1.6083 + *                length.
  1.6084 + *  # dnses     - A space-delimited list of DNS server addresses.
  1.6085 + *  # gateways  - A space-delimited list of default gateway addresses.
  1.6086 + */
  1.6087 +RilObject.prototype.readDataCall_v5 = function(options) {
  1.6088 +  if (!options) {
  1.6089 +    options = {};
  1.6090 +  }
  1.6091 +  let Buf = this.context.Buf;
  1.6092 +  options.cid = Buf.readInt32().toString();
  1.6093 +  options.active = Buf.readInt32(); // DATACALL_ACTIVE_*
  1.6094 +  options.type = Buf.readString();
  1.6095 +  options.apn = Buf.readString();
  1.6096 +  let addresses = Buf.readString();
  1.6097 +  let dnses = Buf.readString();
  1.6098 +  options.addresses = addresses ? addresses.split(" ") : [];
  1.6099 +  options.dnses = dnses ? dnses.split(" ") : [];
  1.6100 +  options.gateways = [];
  1.6101 +  return options;
  1.6102 +};
  1.6103 +
  1.6104 +RilObject.prototype.readDataCall_v6 = function(options) {
  1.6105 +  if (!options) {
  1.6106 +    options = {};
  1.6107 +  }
  1.6108 +  let Buf = this.context.Buf;
  1.6109 +  options.status = Buf.readInt32();  // DATACALL_FAIL_*
  1.6110 +  options.suggestedRetryTime = Buf.readInt32();
  1.6111 +  options.cid = Buf.readInt32().toString();
  1.6112 +  options.active = Buf.readInt32();  // DATACALL_ACTIVE_*
  1.6113 +  options.type = Buf.readString();
  1.6114 +  options.ifname = Buf.readString();
  1.6115 +  let addresses = Buf.readString();
  1.6116 +  let dnses = Buf.readString();
  1.6117 +  let gateways = Buf.readString();
  1.6118 +  options.addresses = addresses ? addresses.split(" ") : [];
  1.6119 +  options.dnses = dnses ? dnses.split(" ") : [];
  1.6120 +  options.gateways = gateways ? gateways.split(" ") : [];
  1.6121 +  return options;
  1.6122 +};
  1.6123 +
  1.6124 +RilObject.prototype[REQUEST_DATA_CALL_LIST] = function REQUEST_DATA_CALL_LIST(length, options) {
  1.6125 +  if (options.rilRequestError) {
  1.6126 +    return;
  1.6127 +  }
  1.6128 +
  1.6129 +  if (!length) {
  1.6130 +    this._processDataCallList(null);
  1.6131 +    return;
  1.6132 +  }
  1.6133 +
  1.6134 +  let Buf = this.context.Buf;
  1.6135 +  let version = 0;
  1.6136 +  if (!this.v5Legacy) {
  1.6137 +    version = Buf.readInt32();
  1.6138 +  }
  1.6139 +  let num = Buf.readInt32();
  1.6140 +  let datacalls = {};
  1.6141 +  for (let i = 0; i < num; i++) {
  1.6142 +    let datacall;
  1.6143 +    if (version < 6) {
  1.6144 +      datacall = this.readDataCall_v5();
  1.6145 +    } else {
  1.6146 +      datacall = this.readDataCall_v6();
  1.6147 +    }
  1.6148 +    datacalls[datacall.cid] = datacall;
  1.6149 +  }
  1.6150 +
  1.6151 +  let newDataCallOptions = null;
  1.6152 +  if (options.rilRequestType == REQUEST_SETUP_DATA_CALL) {
  1.6153 +    newDataCallOptions = options;
  1.6154 +  }
  1.6155 +  this._processDataCallList(datacalls, newDataCallOptions);
  1.6156 +};
  1.6157 +RilObject.prototype[REQUEST_RESET_RADIO] = null;
  1.6158 +RilObject.prototype[REQUEST_OEM_HOOK_RAW] = null;
  1.6159 +RilObject.prototype[REQUEST_OEM_HOOK_STRINGS] = null;
  1.6160 +RilObject.prototype[REQUEST_SCREEN_STATE] = null;
  1.6161 +RilObject.prototype[REQUEST_SET_SUPP_SVC_NOTIFICATION] = null;
  1.6162 +RilObject.prototype[REQUEST_WRITE_SMS_TO_SIM] = function REQUEST_WRITE_SMS_TO_SIM(length, options) {
  1.6163 +  if (options.rilRequestError) {
  1.6164 +    // `The MS shall return a "protocol error, unspecified" error message if
  1.6165 +    // the short message cannot be stored in the (U)SIM, and there is other
  1.6166 +    // message storage available at the MS` ~ 3GPP TS 23.038 section 4. Here
  1.6167 +    // we assume we always have indexed db as another storage.
  1.6168 +    this.acknowledgeGsmSms(false, PDU_FCS_PROTOCOL_ERROR);
  1.6169 +  } else {
  1.6170 +    this.acknowledgeGsmSms(true, PDU_FCS_OK);
  1.6171 +  }
  1.6172 +};
  1.6173 +RilObject.prototype[REQUEST_DELETE_SMS_ON_SIM] = null;
  1.6174 +RilObject.prototype[REQUEST_SET_BAND_MODE] = null;
  1.6175 +RilObject.prototype[REQUEST_QUERY_AVAILABLE_BAND_MODE] = null;
  1.6176 +RilObject.prototype[REQUEST_STK_GET_PROFILE] = null;
  1.6177 +RilObject.prototype[REQUEST_STK_SET_PROFILE] = null;
  1.6178 +RilObject.prototype[REQUEST_STK_SEND_ENVELOPE_COMMAND] = null;
  1.6179 +RilObject.prototype[REQUEST_STK_SEND_TERMINAL_RESPONSE] = null;
  1.6180 +RilObject.prototype[REQUEST_STK_HANDLE_CALL_SETUP_REQUESTED_FROM_SIM] = null;
  1.6181 +RilObject.prototype[REQUEST_EXPLICIT_CALL_TRANSFER] = null;
  1.6182 +RilObject.prototype[REQUEST_SET_PREFERRED_NETWORK_TYPE] = function REQUEST_SET_PREFERRED_NETWORK_TYPE(length, options) {
  1.6183 +  if (options.networkType == null) {
  1.6184 +    // The request was made by ril_worker itself automatically. Don't report.
  1.6185 +    return;
  1.6186 +  }
  1.6187 +
  1.6188 +  if (options.rilRequestError) {
  1.6189 +    options.success = false;
  1.6190 +    options.errorMsg = RIL_ERROR_TO_GECKO_ERROR[options.rilRequestError];
  1.6191 +  } else {
  1.6192 +    options.success = true;
  1.6193 +  }
  1.6194 +  this.sendChromeMessage(options);
  1.6195 +};
  1.6196 +RilObject.prototype[REQUEST_GET_PREFERRED_NETWORK_TYPE] = function REQUEST_GET_PREFERRED_NETWORK_TYPE(length, options) {
  1.6197 +  if (options.rilRequestError) {
  1.6198 +    options.success = false;
  1.6199 +    options.errorMsg = RIL_ERROR_TO_GECKO_ERROR[options.rilRequestError];
  1.6200 +    this.sendChromeMessage(options);
  1.6201 +    return;
  1.6202 +  }
  1.6203 +
  1.6204 +  let results = this.context.Buf.readInt32List();
  1.6205 +  options.networkType = this.preferredNetworkType = results[0];
  1.6206 +  options.success = true;
  1.6207 +
  1.6208 +  this.sendChromeMessage(options);
  1.6209 +};
  1.6210 +RilObject.prototype[REQUEST_GET_NEIGHBORING_CELL_IDS] = null;
  1.6211 +RilObject.prototype[REQUEST_SET_LOCATION_UPDATES] = null;
  1.6212 +RilObject.prototype[REQUEST_CDMA_SET_SUBSCRIPTION_SOURCE] = null;
  1.6213 +RilObject.prototype[REQUEST_CDMA_SET_ROAMING_PREFERENCE] = function REQUEST_CDMA_SET_ROAMING_PREFERENCE(length, options) {
  1.6214 +  if (options.rilRequestError) {
  1.6215 +    options.errorMsg = RIL_ERROR_TO_GECKO_ERROR[options.rilRequestError];
  1.6216 +  }
  1.6217 +  this.sendChromeMessage(options);
  1.6218 +};
  1.6219 +RilObject.prototype[REQUEST_CDMA_QUERY_ROAMING_PREFERENCE] = function REQUEST_CDMA_QUERY_ROAMING_PREFERENCE(length, options) {
  1.6220 +  if (options.rilRequestError) {
  1.6221 +    options.errorMsg = RIL_ERROR_TO_GECKO_ERROR[options.rilRequestError];
  1.6222 +  } else {
  1.6223 +    let mode = this.context.Buf.readInt32List();
  1.6224 +    options.mode = CDMA_ROAMING_PREFERENCE_TO_GECKO[mode[0]];
  1.6225 +  }
  1.6226 +  this.sendChromeMessage(options);
  1.6227 +};
  1.6228 +RilObject.prototype[REQUEST_SET_TTY_MODE] = null;
  1.6229 +RilObject.prototype[REQUEST_QUERY_TTY_MODE] = null;
  1.6230 +RilObject.prototype[REQUEST_CDMA_SET_PREFERRED_VOICE_PRIVACY_MODE] = function REQUEST_CDMA_SET_PREFERRED_VOICE_PRIVACY_MODE(length, options) {
  1.6231 +  if (options.rilRequestError) {
  1.6232 +    options.errorMsg = RIL_ERROR_TO_GECKO_ERROR[options.rilRequestError];
  1.6233 +    this.sendChromeMessage(options);
  1.6234 +    return;
  1.6235 +  }
  1.6236 +
  1.6237 +  this.sendChromeMessage(options);
  1.6238 +};
  1.6239 +RilObject.prototype[REQUEST_CDMA_QUERY_PREFERRED_VOICE_PRIVACY_MODE] = function REQUEST_CDMA_QUERY_PREFERRED_VOICE_PRIVACY_MODE(length, options) {
  1.6240 +  if (options.rilRequestError) {
  1.6241 +    options.errorMsg = RIL_ERROR_TO_GECKO_ERROR[options.rilRequestError];
  1.6242 +    this.sendChromeMessage(options);
  1.6243 +    return;
  1.6244 +  }
  1.6245 +
  1.6246 +  let enabled = this.context.Buf.readInt32List();
  1.6247 +  options.enabled = enabled[0] ? true : false;
  1.6248 +  this.sendChromeMessage(options);
  1.6249 +};
  1.6250 +RilObject.prototype[REQUEST_CDMA_FLASH] = function REQUEST_CDMA_FLASH(length, options) {
  1.6251 +  options.success = (options.rilRequestError === 0);
  1.6252 +  if (!options.success) {
  1.6253 +    if (options.rilMessageType === "conferenceCall") {
  1.6254 +      options.errorName = "addError";
  1.6255 +    } else if (options.rilMessageType === "separateCall") {
  1.6256 +      options.errorName = "removeError";
  1.6257 +    }
  1.6258 +    options.errorMsg = RIL_ERROR_TO_GECKO_ERROR[options.rilRequestError];
  1.6259 +  }
  1.6260 +
  1.6261 +  this.sendChromeMessage(options);
  1.6262 +};
  1.6263 +RilObject.prototype[REQUEST_CDMA_BURST_DTMF] = null;
  1.6264 +RilObject.prototype[REQUEST_CDMA_VALIDATE_AND_WRITE_AKEY] = null;
  1.6265 +RilObject.prototype[REQUEST_CDMA_SEND_SMS] = function REQUEST_CDMA_SEND_SMS(length, options) {
  1.6266 +  this._processSmsSendResult(length, options);
  1.6267 +};
  1.6268 +RilObject.prototype[REQUEST_CDMA_SMS_ACKNOWLEDGE] = null;
  1.6269 +RilObject.prototype[REQUEST_GSM_GET_BROADCAST_SMS_CONFIG] = null;
  1.6270 +RilObject.prototype[REQUEST_GSM_SET_BROADCAST_SMS_CONFIG] = function REQUEST_GSM_SET_BROADCAST_SMS_CONFIG(length, options) {
  1.6271 +  if (options.rilRequestError == ERROR_SUCCESS) {
  1.6272 +    this.setSmsBroadcastActivation(true);
  1.6273 +  }
  1.6274 +};
  1.6275 +RilObject.prototype[REQUEST_GSM_SMS_BROADCAST_ACTIVATION] = null;
  1.6276 +RilObject.prototype[REQUEST_CDMA_GET_BROADCAST_SMS_CONFIG] = null;
  1.6277 +RilObject.prototype[REQUEST_CDMA_SET_BROADCAST_SMS_CONFIG] = null;
  1.6278 +RilObject.prototype[REQUEST_CDMA_SMS_BROADCAST_ACTIVATION] = null;
  1.6279 +RilObject.prototype[REQUEST_CDMA_SUBSCRIPTION] = function REQUEST_CDMA_SUBSCRIPTION(length, options) {
  1.6280 +  if (options.rilRequestError) {
  1.6281 +    return;
  1.6282 +  }
  1.6283 +
  1.6284 +  let result = this.context.Buf.readStringList();
  1.6285 +
  1.6286 +  this.iccInfo.mdn = result[0];
  1.6287 +  // The result[1] is Home SID. (Already be handled in readCDMAHome())
  1.6288 +  // The result[2] is Home NID. (Already be handled in readCDMAHome())
  1.6289 +  // The result[3] is MIN.
  1.6290 +  this.iccInfo.prlVersion = parseInt(result[4], 10);
  1.6291 +
  1.6292 +  this.context.ICCUtilsHelper.handleICCInfoChange();
  1.6293 +};
  1.6294 +RilObject.prototype[REQUEST_CDMA_WRITE_SMS_TO_RUIM] = null;
  1.6295 +RilObject.prototype[REQUEST_CDMA_DELETE_SMS_ON_RUIM] = null;
  1.6296 +RilObject.prototype[REQUEST_DEVICE_IDENTITY] = function REQUEST_DEVICE_IDENTITY(length, options) {
  1.6297 +  if (options.rilRequestError) {
  1.6298 +    return;
  1.6299 +  }
  1.6300 +
  1.6301 +  let result = this.context.Buf.readStringList();
  1.6302 +
  1.6303 +  // The result[0] is for IMEI. (Already be handled in REQUEST_GET_IMEI)
  1.6304 +  // The result[1] is for IMEISV. (Already be handled in REQUEST_GET_IMEISV)
  1.6305 +  // They are both ignored.
  1.6306 +  this.ESN = result[2];
  1.6307 +  this.MEID = result[3];
  1.6308 +};
  1.6309 +RilObject.prototype[REQUEST_EXIT_EMERGENCY_CALLBACK_MODE] = function REQUEST_EXIT_EMERGENCY_CALLBACK_MODE(length, options) {
  1.6310 +  if (options.internal) {
  1.6311 +    return;
  1.6312 +  }
  1.6313 +
  1.6314 +  options.success = (options.rilRequestError === 0);
  1.6315 +  if (!options.success) {
  1.6316 +    options.errorMsg = RIL_ERROR_TO_GECKO_ERROR[options.rilRequestError];
  1.6317 +  }
  1.6318 +  this.sendChromeMessage(options);
  1.6319 +};
  1.6320 +RilObject.prototype[REQUEST_GET_SMSC_ADDRESS] = function REQUEST_GET_SMSC_ADDRESS(length, options) {
  1.6321 +  this.SMSC = options.rilRequestError ? null : this.context.Buf.readString();
  1.6322 +
  1.6323 +  if (!options.rilMessageType || options.rilMessageType !== "getSmscAddress") {
  1.6324 +    return;
  1.6325 +  }
  1.6326 +
  1.6327 +  options.smscAddress = this.SMSC;
  1.6328 +  options.errorMsg = RIL_ERROR_TO_GECKO_ERROR[options.rilRequestError];
  1.6329 +  this.sendChromeMessage(options);
  1.6330 +};
  1.6331 +RilObject.prototype[REQUEST_SET_SMSC_ADDRESS] = null;
  1.6332 +RilObject.prototype[REQUEST_REPORT_SMS_MEMORY_STATUS] = null;
  1.6333 +RilObject.prototype[REQUEST_REPORT_STK_SERVICE_IS_RUNNING] = null;
  1.6334 +RilObject.prototype[REQUEST_ACKNOWLEDGE_INCOMING_GSM_SMS_WITH_PDU] = null;
  1.6335 +RilObject.prototype[REQUEST_STK_SEND_ENVELOPE_WITH_STATUS] = function REQUEST_STK_SEND_ENVELOPE_WITH_STATUS(length, options) {
  1.6336 +  if (options.rilRequestError) {
  1.6337 +    this.acknowledgeGsmSms(false, PDU_FCS_UNSPECIFIED);
  1.6338 +    return;
  1.6339 +  }
  1.6340 +
  1.6341 +  let Buf = this.context.Buf;
  1.6342 +  let sw1 = Buf.readInt32();
  1.6343 +  let sw2 = Buf.readInt32();
  1.6344 +  if ((sw1 == ICC_STATUS_SAT_BUSY) && (sw2 === 0x00)) {
  1.6345 +    this.acknowledgeGsmSms(false, PDU_FCS_USAT_BUSY);
  1.6346 +    return;
  1.6347 +  }
  1.6348 +
  1.6349 +  let success = ((sw1 == ICC_STATUS_NORMAL_ENDING) && (sw2 === 0x00))
  1.6350 +                || (sw1 == ICC_STATUS_NORMAL_ENDING_WITH_EXTRA);
  1.6351 +
  1.6352 +  let messageStringLength = Buf.readInt32(); // In semi-octets
  1.6353 +  let responsePduLen = messageStringLength / 2; // In octets
  1.6354 +  if (!responsePduLen) {
  1.6355 +    this.acknowledgeGsmSms(success, success ? PDU_FCS_OK
  1.6356 +                                         : PDU_FCS_USIM_DATA_DOWNLOAD_ERROR);
  1.6357 +    return;
  1.6358 +  }
  1.6359 +
  1.6360 +  this.acknowledgeIncomingGsmSmsWithPDU(success, responsePduLen, options);
  1.6361 +};
  1.6362 +RilObject.prototype[REQUEST_VOICE_RADIO_TECH] = function REQUEST_VOICE_RADIO_TECH(length, options) {
  1.6363 +  if (options.rilRequestError) {
  1.6364 +    if (DEBUG) {
  1.6365 +      this.context.debug("Error when getting voice radio tech: " +
  1.6366 +                         options.rilRequestError);
  1.6367 +    }
  1.6368 +    return;
  1.6369 +  }
  1.6370 +  let radioTech = this.context.Buf.readInt32List();
  1.6371 +  this._processRadioTech(radioTech[0]);
  1.6372 +};
  1.6373 +RilObject.prototype[REQUEST_GET_UNLOCK_RETRY_COUNT] = function REQUEST_GET_UNLOCK_RETRY_COUNT(length, options) {
  1.6374 +  options.success = (options.rilRequestError === 0);
  1.6375 +  if (!options.success) {
  1.6376 +    options.errorMsg = RIL_ERROR_TO_GECKO_ERROR[options.rilRequestError];
  1.6377 +  }
  1.6378 +  options.retryCount = length ? this.context.Buf.readInt32List()[0] : -1;
  1.6379 +  this.sendChromeMessage(options);
  1.6380 +};
  1.6381 +RilObject.prototype[RIL_REQUEST_GPRS_ATTACH] = null;
  1.6382 +RilObject.prototype[RIL_REQUEST_GPRS_DETACH] = null;
  1.6383 +RilObject.prototype[UNSOLICITED_RESPONSE_RADIO_STATE_CHANGED] = function UNSOLICITED_RESPONSE_RADIO_STATE_CHANGED() {
  1.6384 +  let radioState = this.context.Buf.readInt32();
  1.6385 +  let newState;
  1.6386 +  if (radioState == RADIO_STATE_UNAVAILABLE) {
  1.6387 +    newState = GECKO_RADIOSTATE_UNAVAILABLE;
  1.6388 +  } else if (radioState == RADIO_STATE_OFF) {
  1.6389 +    newState = GECKO_RADIOSTATE_OFF;
  1.6390 +  } else {
  1.6391 +    newState = GECKO_RADIOSTATE_READY;
  1.6392 +  }
  1.6393 +
  1.6394 +  if (DEBUG) {
  1.6395 +    this.context.debug("Radio state changed from '" + this.radioState +
  1.6396 +                       "' to '" + newState + "'");
  1.6397 +  }
  1.6398 +  if (this.radioState == newState) {
  1.6399 +    return;
  1.6400 +  }
  1.6401 +
  1.6402 +  switch (radioState) {
  1.6403 +  case RADIO_STATE_SIM_READY:
  1.6404 +  case RADIO_STATE_SIM_NOT_READY:
  1.6405 +  case RADIO_STATE_SIM_LOCKED_OR_ABSENT:
  1.6406 +    this._isCdma = false;
  1.6407 +    this._waitingRadioTech = false;
  1.6408 +    break;
  1.6409 +  case RADIO_STATE_RUIM_READY:
  1.6410 +  case RADIO_STATE_RUIM_NOT_READY:
  1.6411 +  case RADIO_STATE_RUIM_LOCKED_OR_ABSENT:
  1.6412 +  case RADIO_STATE_NV_READY:
  1.6413 +  case RADIO_STATE_NV_NOT_READY:
  1.6414 +    this._isCdma = true;
  1.6415 +    this._waitingRadioTech = false;
  1.6416 +    break;
  1.6417 +  case RADIO_STATE_ON: // RIL v7
  1.6418 +    // This value is defined in RIL v7, we will retrieve radio tech by another
  1.6419 +    // request. We leave _isCdma untouched, and it will be set once we get the
  1.6420 +    // radio technology.
  1.6421 +    this._waitingRadioTech = true;
  1.6422 +    this.getVoiceRadioTechnology();
  1.6423 +    break;
  1.6424 +  }
  1.6425 +
  1.6426 +  if ((this.radioState == GECKO_RADIOSTATE_UNAVAILABLE ||
  1.6427 +       this.radioState == GECKO_RADIOSTATE_OFF) &&
  1.6428 +       newState == GECKO_RADIOSTATE_READY) {
  1.6429 +    // The radio became available, let's get its info.
  1.6430 +    if (!this._waitingRadioTech) {
  1.6431 +      if (this._isCdma) {
  1.6432 +        this.getDeviceIdentity();
  1.6433 +      } else {
  1.6434 +        this.getIMEI();
  1.6435 +        this.getIMEISV();
  1.6436 +      }
  1.6437 +    }
  1.6438 +    this.getBasebandVersion();
  1.6439 +    this.updateCellBroadcastConfig();
  1.6440 +    this.setPreferredNetworkType();
  1.6441 +    this.setCLIR();
  1.6442 +    if (RILQUIRKS_DATA_REGISTRATION_ON_DEMAND && this._attachDataRegistration) {
  1.6443 +      this.setDataRegistration({attach: true});
  1.6444 +    }
  1.6445 +  }
  1.6446 +
  1.6447 +  this.radioState = newState;
  1.6448 +  this.sendChromeMessage({
  1.6449 +    rilMessageType: "radiostatechange",
  1.6450 +    radioState: newState
  1.6451 +  });
  1.6452 +
  1.6453 +  // If the radio is up and on, so let's query the card state.
  1.6454 +  // On older RILs only if the card is actually ready, though.
  1.6455 +  // If _waitingRadioTech is set, we don't need to get icc status now.
  1.6456 +  if (radioState == RADIO_STATE_UNAVAILABLE ||
  1.6457 +      radioState == RADIO_STATE_OFF ||
  1.6458 +      this._waitingRadioTech) {
  1.6459 +    return;
  1.6460 +  }
  1.6461 +  this.getICCStatus();
  1.6462 +};
  1.6463 +RilObject.prototype[UNSOLICITED_RESPONSE_CALL_STATE_CHANGED] = function UNSOLICITED_RESPONSE_CALL_STATE_CHANGED() {
  1.6464 +  this.getCurrentCalls();
  1.6465 +};
  1.6466 +RilObject.prototype[UNSOLICITED_RESPONSE_VOICE_NETWORK_STATE_CHANGED] = function UNSOLICITED_RESPONSE_VOICE_NETWORK_STATE_CHANGED() {
  1.6467 +  if (DEBUG) {
  1.6468 +    this.context.debug("Network state changed, re-requesting phone state and " +
  1.6469 +                       "ICC status");
  1.6470 +  }
  1.6471 +  this.getICCStatus();
  1.6472 +  this.requestNetworkInfo();
  1.6473 +};
  1.6474 +RilObject.prototype[UNSOLICITED_RESPONSE_NEW_SMS] = function UNSOLICITED_RESPONSE_NEW_SMS(length) {
  1.6475 +  let [message, result] = this.context.GsmPDUHelper.processReceivedSms(length);
  1.6476 +
  1.6477 +  if (message) {
  1.6478 +    result = this._processSmsMultipart(message);
  1.6479 +  }
  1.6480 +
  1.6481 +  if (result == PDU_FCS_RESERVED || result == MOZ_FCS_WAIT_FOR_EXPLICIT_ACK) {
  1.6482 +    return;
  1.6483 +  }
  1.6484 +
  1.6485 +  // Not reserved FCS values, send ACK now.
  1.6486 +  this.acknowledgeGsmSms(result == PDU_FCS_OK, result);
  1.6487 +};
  1.6488 +RilObject.prototype[UNSOLICITED_RESPONSE_NEW_SMS_STATUS_REPORT] = function UNSOLICITED_RESPONSE_NEW_SMS_STATUS_REPORT(length) {
  1.6489 +  let result = this._processSmsStatusReport(length);
  1.6490 +  this.acknowledgeGsmSms(result == PDU_FCS_OK, result);
  1.6491 +};
  1.6492 +RilObject.prototype[UNSOLICITED_RESPONSE_NEW_SMS_ON_SIM] = function UNSOLICITED_RESPONSE_NEW_SMS_ON_SIM(length) {
  1.6493 +  let recordNumber = this.context.Buf.readInt32List()[0];
  1.6494 +
  1.6495 +  this.context.SimRecordHelper.readSMS(
  1.6496 +    recordNumber,
  1.6497 +    function onsuccess(message) {
  1.6498 +      if (message && message.simStatus === 3) { //New Unread SMS
  1.6499 +        this._processSmsMultipart(message);
  1.6500 +      }
  1.6501 +    }.bind(this),
  1.6502 +    function onerror(errorMsg) {
  1.6503 +      if (DEBUG) {
  1.6504 +        this.context.debug("Failed to Read NEW SMS on SIM #" + recordNumber +
  1.6505 +                           ", errorMsg: " + errorMsg);
  1.6506 +      }
  1.6507 +    });
  1.6508 +};
  1.6509 +RilObject.prototype[UNSOLICITED_ON_USSD] = function UNSOLICITED_ON_USSD() {
  1.6510 +  let [typeCode, message] = this.context.Buf.readStringList();
  1.6511 +  if (DEBUG) {
  1.6512 +    this.context.debug("On USSD. Type Code: " + typeCode + " Message: " + message);
  1.6513 +  }
  1.6514 +
  1.6515 +  this._ussdSession = (typeCode != "0" && typeCode != "2");
  1.6516 +
  1.6517 +  this.sendChromeMessage({rilMessageType: "USSDReceived",
  1.6518 +                          message: message,
  1.6519 +                          sessionEnded: !this._ussdSession});
  1.6520 +};
  1.6521 +RilObject.prototype[UNSOLICITED_NITZ_TIME_RECEIVED] = function UNSOLICITED_NITZ_TIME_RECEIVED() {
  1.6522 +  let dateString = this.context.Buf.readString();
  1.6523 +
  1.6524 +  // The data contained in the NITZ message is
  1.6525 +  // in the form "yy/mm/dd,hh:mm:ss(+/-)tz,dt"
  1.6526 +  // for example: 12/02/16,03:36:08-20,00,310410
  1.6527 +  // See also bug 714352 - Listen for NITZ updates from rild.
  1.6528 +
  1.6529 +  if (DEBUG) this.context.debug("DateTimeZone string " + dateString);
  1.6530 +
  1.6531 +  let now = Date.now();
  1.6532 +
  1.6533 +  let year = parseInt(dateString.substr(0, 2), 10);
  1.6534 +  let month = parseInt(dateString.substr(3, 2), 10);
  1.6535 +  let day = parseInt(dateString.substr(6, 2), 10);
  1.6536 +  let hours = parseInt(dateString.substr(9, 2), 10);
  1.6537 +  let minutes = parseInt(dateString.substr(12, 2), 10);
  1.6538 +  let seconds = parseInt(dateString.substr(15, 2), 10);
  1.6539 +  // Note that |tz| is in 15-min units.
  1.6540 +  let tz = parseInt(dateString.substr(17, 3), 10);
  1.6541 +  // Note that |dst| is in 1-hour units and is already applied in |tz|.
  1.6542 +  let dst = parseInt(dateString.substr(21, 2), 10);
  1.6543 +
  1.6544 +  let timeInMS = Date.UTC(year + PDU_TIMESTAMP_YEAR_OFFSET, month - 1, day,
  1.6545 +                          hours, minutes, seconds);
  1.6546 +
  1.6547 +  if (isNaN(timeInMS)) {
  1.6548 +    if (DEBUG) this.context.debug("NITZ failed to convert date");
  1.6549 +    return;
  1.6550 +  }
  1.6551 +
  1.6552 +  this.sendChromeMessage({rilMessageType: "nitzTime",
  1.6553 +                          networkTimeInMS: timeInMS,
  1.6554 +                          networkTimeZoneInMinutes: -(tz * 15),
  1.6555 +                          networkDSTInMinutes: -(dst * 60),
  1.6556 +                          receiveTimeInMS: now});
  1.6557 +};
  1.6558 +
  1.6559 +RilObject.prototype[UNSOLICITED_SIGNAL_STRENGTH] = function UNSOLICITED_SIGNAL_STRENGTH(length) {
  1.6560 +  this[REQUEST_SIGNAL_STRENGTH](length, {rilRequestError: ERROR_SUCCESS});
  1.6561 +};
  1.6562 +RilObject.prototype[UNSOLICITED_DATA_CALL_LIST_CHANGED] = function UNSOLICITED_DATA_CALL_LIST_CHANGED(length) {
  1.6563 +  if (this.v5Legacy) {
  1.6564 +    this.getDataCallList();
  1.6565 +    return;
  1.6566 +  }
  1.6567 +  this[REQUEST_DATA_CALL_LIST](length, {rilRequestError: ERROR_SUCCESS});
  1.6568 +};
  1.6569 +RilObject.prototype[UNSOLICITED_SUPP_SVC_NOTIFICATION] = function UNSOLICITED_SUPP_SVC_NOTIFICATION(length) {
  1.6570 +  let Buf = this.context.Buf;
  1.6571 +  let info = {};
  1.6572 +  info.notificationType = Buf.readInt32();
  1.6573 +  info.code = Buf.readInt32();
  1.6574 +  info.index = Buf.readInt32();
  1.6575 +  info.type = Buf.readInt32();
  1.6576 +  info.number = Buf.readString();
  1.6577 +
  1.6578 +  this._processSuppSvcNotification(info);
  1.6579 +};
  1.6580 +
  1.6581 +RilObject.prototype[UNSOLICITED_STK_SESSION_END] = function UNSOLICITED_STK_SESSION_END() {
  1.6582 +  this.sendChromeMessage({rilMessageType: "stksessionend"});
  1.6583 +};
  1.6584 +RilObject.prototype[UNSOLICITED_STK_PROACTIVE_COMMAND] = function UNSOLICITED_STK_PROACTIVE_COMMAND() {
  1.6585 +  this.processStkProactiveCommand();
  1.6586 +};
  1.6587 +RilObject.prototype[UNSOLICITED_STK_EVENT_NOTIFY] = function UNSOLICITED_STK_EVENT_NOTIFY() {
  1.6588 +  this.processStkProactiveCommand();
  1.6589 +};
  1.6590 +RilObject.prototype[UNSOLICITED_STK_CALL_SETUP] = null;
  1.6591 +RilObject.prototype[UNSOLICITED_SIM_SMS_STORAGE_FULL] = null;
  1.6592 +RilObject.prototype[UNSOLICITED_SIM_REFRESH] = null;
  1.6593 +RilObject.prototype[UNSOLICITED_CALL_RING] = function UNSOLICITED_CALL_RING() {
  1.6594 +  let Buf = this.context.Buf;
  1.6595 +  let info = {rilMessageType: "callRing"};
  1.6596 +  let isCDMA = false; //XXX TODO hard-code this for now
  1.6597 +  if (isCDMA) {
  1.6598 +    info.isPresent = Buf.readInt32();
  1.6599 +    info.signalType = Buf.readInt32();
  1.6600 +    info.alertPitch = Buf.readInt32();
  1.6601 +    info.signal = Buf.readInt32();
  1.6602 +  }
  1.6603 +  // At this point we don't know much other than the fact there's an incoming
  1.6604 +  // call, but that's enough to bring up the Phone app already. We'll know
  1.6605 +  // details once we get a call state changed notification and can then
  1.6606 +  // dispatch DOM events etc.
  1.6607 +  this.sendChromeMessage(info);
  1.6608 +};
  1.6609 +RilObject.prototype[UNSOLICITED_RESPONSE_SIM_STATUS_CHANGED] = function UNSOLICITED_RESPONSE_SIM_STATUS_CHANGED() {
  1.6610 +  this.getICCStatus();
  1.6611 +};
  1.6612 +RilObject.prototype[UNSOLICITED_RESPONSE_CDMA_NEW_SMS] = function UNSOLICITED_RESPONSE_CDMA_NEW_SMS(length) {
  1.6613 +  let [message, result] = this.context.CdmaPDUHelper.processReceivedSms(length);
  1.6614 +
  1.6615 +  if (message) {
  1.6616 +    if (message.teleservice === PDU_CDMA_MSG_TELESERIVCIE_ID_WAP) {
  1.6617 +      result = this._processCdmaSmsWapPush(message);
  1.6618 +    } else if (message.subMsgType === PDU_CDMA_MSG_TYPE_DELIVER_ACK) {
  1.6619 +      result = this._processCdmaSmsStatusReport(message);
  1.6620 +    } else {
  1.6621 +      result = this._processSmsMultipart(message);
  1.6622 +    }
  1.6623 +  }
  1.6624 +
  1.6625 +  if (result == PDU_FCS_RESERVED || result == MOZ_FCS_WAIT_FOR_EXPLICIT_ACK) {
  1.6626 +    return;
  1.6627 +  }
  1.6628 +
  1.6629 +  // Not reserved FCS values, send ACK now.
  1.6630 +  this.acknowledgeCdmaSms(result == PDU_FCS_OK, result);
  1.6631 +};
  1.6632 +RilObject.prototype[UNSOLICITED_RESPONSE_NEW_BROADCAST_SMS] = function UNSOLICITED_RESPONSE_NEW_BROADCAST_SMS(length) {
  1.6633 +  let message;
  1.6634 +  try {
  1.6635 +    message =
  1.6636 +      this.context.GsmPDUHelper.readCbMessage(this.context.Buf.readInt32());
  1.6637 +  } catch (e) {
  1.6638 +    if (DEBUG) {
  1.6639 +      this.context.debug("Failed to parse Cell Broadcast message: " +
  1.6640 +                         JSON.stringify(e));
  1.6641 +    }
  1.6642 +    return;
  1.6643 +  }
  1.6644 +
  1.6645 +  message = this._processReceivedSmsCbPage(message);
  1.6646 +  if (!message) {
  1.6647 +    return;
  1.6648 +  }
  1.6649 +
  1.6650 +  message.rilMessageType = "cellbroadcast-received";
  1.6651 +  this.sendChromeMessage(message);
  1.6652 +};
  1.6653 +RilObject.prototype[UNSOLICITED_CDMA_RUIM_SMS_STORAGE_FULL] = null;
  1.6654 +RilObject.prototype[UNSOLICITED_RESTRICTED_STATE_CHANGED] = null;
  1.6655 +RilObject.prototype[UNSOLICITED_ENTER_EMERGENCY_CALLBACK_MODE] = function UNSOLICITED_ENTER_EMERGENCY_CALLBACK_MODE() {
  1.6656 +  this._handleChangedEmergencyCbMode(true);
  1.6657 +};
  1.6658 +RilObject.prototype[UNSOLICITED_CDMA_CALL_WAITING] = function UNSOLICITED_CDMA_CALL_WAITING(length) {
  1.6659 +  let Buf = this.context.Buf;
  1.6660 +  let call = {};
  1.6661 +  call.number              = Buf.readString();
  1.6662 +  call.numberPresentation  = Buf.readInt32();
  1.6663 +  call.name                = Buf.readString();
  1.6664 +  call.namePresentation    = Buf.readInt32();
  1.6665 +  call.isPresent           = Buf.readInt32();
  1.6666 +  call.signalType          = Buf.readInt32();
  1.6667 +  call.alertPitch          = Buf.readInt32();
  1.6668 +  call.signal              = Buf.readInt32();
  1.6669 +  this.sendChromeMessage({rilMessageType: "cdmaCallWaiting",
  1.6670 +                          number: call.number});
  1.6671 +};
  1.6672 +RilObject.prototype[UNSOLICITED_CDMA_OTA_PROVISION_STATUS] = function UNSOLICITED_CDMA_OTA_PROVISION_STATUS() {
  1.6673 +  let status = this.context.Buf.readInt32List()[0];
  1.6674 +  this.sendChromeMessage({rilMessageType: "otastatuschange",
  1.6675 +                          status: status});
  1.6676 +};
  1.6677 +RilObject.prototype[UNSOLICITED_CDMA_INFO_REC] = function UNSOLICITED_CDMA_INFO_REC(length) {
  1.6678 +  let record = this.context.CdmaPDUHelper.decodeInformationRecord();
  1.6679 +  record.rilMessageType = "cdma-info-rec-received";
  1.6680 +  this.sendChromeMessage(record);
  1.6681 +};
  1.6682 +RilObject.prototype[UNSOLICITED_OEM_HOOK_RAW] = null;
  1.6683 +RilObject.prototype[UNSOLICITED_RINGBACK_TONE] = null;
  1.6684 +RilObject.prototype[UNSOLICITED_RESEND_INCALL_MUTE] = null;
  1.6685 +RilObject.prototype[UNSOLICITED_CDMA_SUBSCRIPTION_SOURCE_CHANGED] = null;
  1.6686 +RilObject.prototype[UNSOLICITED_CDMA_PRL_CHANGED] = function UNSOLICITED_CDMA_PRL_CHANGED(length) {
  1.6687 +  let version = this.context.Buf.readInt32List()[0];
  1.6688 +  if (version !== this.iccInfo.prlVersion) {
  1.6689 +    this.iccInfo.prlVersion = version;
  1.6690 +    this.context.ICCUtilsHelper.handleICCInfoChange();
  1.6691 +  }
  1.6692 +};
  1.6693 +RilObject.prototype[UNSOLICITED_EXIT_EMERGENCY_CALLBACK_MODE] = function UNSOLICITED_EXIT_EMERGENCY_CALLBACK_MODE() {
  1.6694 +  this._handleChangedEmergencyCbMode(false);
  1.6695 +};
  1.6696 +RilObject.prototype[UNSOLICITED_RIL_CONNECTED] = function UNSOLICITED_RIL_CONNECTED(length) {
  1.6697 +  // Prevent response id collision between UNSOLICITED_RIL_CONNECTED and
  1.6698 +  // UNSOLICITED_VOICE_RADIO_TECH_CHANGED for Akami on gingerbread branch.
  1.6699 +  if (!length) {
  1.6700 +    return;
  1.6701 +  }
  1.6702 +
  1.6703 +  let version = this.context.Buf.readInt32List()[0];
  1.6704 +  this.v5Legacy = (version < 5);
  1.6705 +  if (DEBUG) {
  1.6706 +    this.context.debug("Detected RIL version " + version);
  1.6707 +    this.context.debug("this.v5Legacy is " + this.v5Legacy);
  1.6708 +  }
  1.6709 +
  1.6710 +  this.initRILState();
  1.6711 +  // Always ensure that we are not in emergency callback mode when init.
  1.6712 +  this.exitEmergencyCbMode();
  1.6713 +  // Reset radio in the case that b2g restart (or crash).
  1.6714 +  this.setRadioEnabled({enabled: false});
  1.6715 +};
  1.6716 +
  1.6717 +/**
  1.6718 + * This object exposes the functionality to parse and serialize PDU strings
  1.6719 + *
  1.6720 + * A PDU is a string containing a series of hexadecimally encoded octets
  1.6721 + * or nibble-swapped binary-coded decimals (BCDs). It contains not only the
  1.6722 + * message text but information about the sender, the SMS service center,
  1.6723 + * timestamp, etc.
  1.6724 + */
  1.6725 +function GsmPDUHelperObject(aContext) {
  1.6726 +  this.context = aContext;
  1.6727 +}
  1.6728 +GsmPDUHelperObject.prototype = {
  1.6729 +  context: null,
  1.6730 +
  1.6731 +  /**
  1.6732 +   * Read one character (2 bytes) from a RIL string and decode as hex.
  1.6733 +   *
  1.6734 +   * @return the nibble as a number.
  1.6735 +   */
  1.6736 +  readHexNibble: function() {
  1.6737 +    let nibble = this.context.Buf.readUint16();
  1.6738 +    if (nibble >= 48 && nibble <= 57) {
  1.6739 +      nibble -= 48; // ASCII '0'..'9'
  1.6740 +    } else if (nibble >= 65 && nibble <= 70) {
  1.6741 +      nibble -= 55; // ASCII 'A'..'F'
  1.6742 +    } else if (nibble >= 97 && nibble <= 102) {
  1.6743 +      nibble -= 87; // ASCII 'a'..'f'
  1.6744 +    } else {
  1.6745 +      throw "Found invalid nibble during PDU parsing: " +
  1.6746 +            String.fromCharCode(nibble);
  1.6747 +    }
  1.6748 +    return nibble;
  1.6749 +  },
  1.6750 +
  1.6751 +  /**
  1.6752 +   * Encode a nibble as one hex character in a RIL string (2 bytes).
  1.6753 +   *
  1.6754 +   * @param nibble
  1.6755 +   *        The nibble to encode (represented as a number)
  1.6756 +   */
  1.6757 +  writeHexNibble: function(nibble) {
  1.6758 +    nibble &= 0x0f;
  1.6759 +    if (nibble < 10) {
  1.6760 +      nibble += 48; // ASCII '0'
  1.6761 +    } else {
  1.6762 +      nibble += 55; // ASCII 'A'
  1.6763 +    }
  1.6764 +    this.context.Buf.writeUint16(nibble);
  1.6765 +  },
  1.6766 +
  1.6767 +  /**
  1.6768 +   * Read a hex-encoded octet (two nibbles).
  1.6769 +   *
  1.6770 +   * @return the octet as a number.
  1.6771 +   */
  1.6772 +  readHexOctet: function() {
  1.6773 +    return (this.readHexNibble() << 4) | this.readHexNibble();
  1.6774 +  },
  1.6775 +
  1.6776 +  /**
  1.6777 +   * Write an octet as two hex-encoded nibbles.
  1.6778 +   *
  1.6779 +   * @param octet
  1.6780 +   *        The octet (represented as a number) to encode.
  1.6781 +   */
  1.6782 +  writeHexOctet: function(octet) {
  1.6783 +    this.writeHexNibble(octet >> 4);
  1.6784 +    this.writeHexNibble(octet);
  1.6785 +  },
  1.6786 +
  1.6787 +  /**
  1.6788 +   * Read an array of hex-encoded octets.
  1.6789 +   */
  1.6790 +  readHexOctetArray: function(length) {
  1.6791 +    let array = new Uint8Array(length);
  1.6792 +    for (let i = 0; i < length; i++) {
  1.6793 +      array[i] = this.readHexOctet();
  1.6794 +    }
  1.6795 +    return array;
  1.6796 +  },
  1.6797 +
  1.6798 +  /**
  1.6799 +   * Convert an octet (number) to a BCD number.
  1.6800 +   *
  1.6801 +   * Any nibbles that are not in the BCD range count as 0.
  1.6802 +   *
  1.6803 +   * @param octet
  1.6804 +   *        The octet (a number, as returned by getOctet())
  1.6805 +   *
  1.6806 +   * @return the corresponding BCD number.
  1.6807 +   */
  1.6808 +  octetToBCD: function(octet) {
  1.6809 +    return ((octet & 0xf0) <= 0x90) * ((octet >> 4) & 0x0f) +
  1.6810 +           ((octet & 0x0f) <= 0x09) * (octet & 0x0f) * 10;
  1.6811 +  },
  1.6812 +
  1.6813 +  /**
  1.6814 +   * Convert a BCD number to an octet (number)
  1.6815 +   *
  1.6816 +   * Only take two digits with absolute value.
  1.6817 +   *
  1.6818 +   * @param bcd
  1.6819 +   *
  1.6820 +   * @return the corresponding octet.
  1.6821 +   */
  1.6822 +  BCDToOctet: function(bcd) {
  1.6823 +    bcd = Math.abs(bcd);
  1.6824 +    return ((bcd % 10) << 4) + (Math.floor(bcd / 10) % 10);
  1.6825 +  },
  1.6826 +
  1.6827 +  /**
  1.6828 +   * Convert a semi-octet (number) to a GSM BCD char, or return empty string
  1.6829 +   * if invalid semiOctet and supressException is set to true.
  1.6830 +   *
  1.6831 +   * @param semiOctet
  1.6832 +   *        Nibble to be converted to.
  1.6833 +   * @param [optional] supressException
  1.6834 +   *        Supress exception if invalid semiOctet and supressException is set
  1.6835 +   *        to true.
  1.6836 +   *
  1.6837 +   * @return GSM BCD char, or empty string.
  1.6838 +   */
  1.6839 +  bcdChars: "0123456789*#,;",
  1.6840 +  semiOctetToBcdChar: function(semiOctet, supressException) {
  1.6841 +    if (semiOctet >= 14) {
  1.6842 +      if (supressException) {
  1.6843 +        return "";
  1.6844 +      } else {
  1.6845 +        throw new RangeError();
  1.6846 +      }
  1.6847 +    }
  1.6848 +
  1.6849 +    return this.bcdChars.charAt(semiOctet);
  1.6850 +  },
  1.6851 +
  1.6852 +  /**
  1.6853 +   * Read a *swapped nibble* binary coded decimal (BCD)
  1.6854 +   *
  1.6855 +   * @param pairs
  1.6856 +   *        Number of nibble *pairs* to read.
  1.6857 +   *
  1.6858 +   * @return the decimal as a number.
  1.6859 +   */
  1.6860 +  readSwappedNibbleBcdNum: function(pairs) {
  1.6861 +    let number = 0;
  1.6862 +    for (let i = 0; i < pairs; i++) {
  1.6863 +      let octet = this.readHexOctet();
  1.6864 +      // Ignore 'ff' octets as they're often used as filler.
  1.6865 +      if (octet == 0xff) {
  1.6866 +        continue;
  1.6867 +      }
  1.6868 +      // If the first nibble is an "F" , only the second nibble is to be taken
  1.6869 +      // into account.
  1.6870 +      if ((octet & 0xf0) == 0xf0) {
  1.6871 +        number *= 10;
  1.6872 +        number += octet & 0x0f;
  1.6873 +        continue;
  1.6874 +      }
  1.6875 +      number *= 100;
  1.6876 +      number += this.octetToBCD(octet);
  1.6877 +    }
  1.6878 +    return number;
  1.6879 +  },
  1.6880 +
  1.6881 +  /**
  1.6882 +   * Read a *swapped nibble* binary coded string (BCD)
  1.6883 +   *
  1.6884 +   * @param pairs
  1.6885 +   *        Number of nibble *pairs* to read.
  1.6886 +   * @param [optional] supressException
  1.6887 +   *        Supress exception if invalid semiOctet and supressException is set
  1.6888 +   *        to true.
  1.6889 +   *
  1.6890 +   * @return The BCD string.
  1.6891 +   */
  1.6892 +  readSwappedNibbleBcdString: function(pairs, supressException) {
  1.6893 +    let str = "";
  1.6894 +    for (let i = 0; i < pairs; i++) {
  1.6895 +      let nibbleH = this.readHexNibble();
  1.6896 +      let nibbleL = this.readHexNibble();
  1.6897 +      if (nibbleL == 0x0F) {
  1.6898 +        break;
  1.6899 +      }
  1.6900 +
  1.6901 +      str += this.semiOctetToBcdChar(nibbleL, supressException);
  1.6902 +      if (nibbleH != 0x0F) {
  1.6903 +        str += this.semiOctetToBcdChar(nibbleH, supressException);
  1.6904 +      }
  1.6905 +    }
  1.6906 +
  1.6907 +    return str;
  1.6908 +  },
  1.6909 +
  1.6910 +  /**
  1.6911 +   * Write numerical data as swapped nibble BCD.
  1.6912 +   *
  1.6913 +   * @param data
  1.6914 +   *        Data to write (as a string or a number)
  1.6915 +   */
  1.6916 +  writeSwappedNibbleBCD: function(data) {
  1.6917 +    data = data.toString();
  1.6918 +    if (data.length % 2) {
  1.6919 +      data += "F";
  1.6920 +    }
  1.6921 +    let Buf = this.context.Buf;
  1.6922 +    for (let i = 0; i < data.length; i += 2) {
  1.6923 +      Buf.writeUint16(data.charCodeAt(i + 1));
  1.6924 +      Buf.writeUint16(data.charCodeAt(i));
  1.6925 +    }
  1.6926 +  },
  1.6927 +
  1.6928 +  /**
  1.6929 +   * Write numerical data as swapped nibble BCD.
  1.6930 +   * If the number of digit of data is even, add '0' at the beginning.
  1.6931 +   *
  1.6932 +   * @param data
  1.6933 +   *        Data to write (as a string or a number)
  1.6934 +   */
  1.6935 +  writeSwappedNibbleBCDNum: function(data) {
  1.6936 +    data = data.toString();
  1.6937 +    if (data.length % 2) {
  1.6938 +      data = "0" + data;
  1.6939 +    }
  1.6940 +    let Buf = this.context.Buf;
  1.6941 +    for (let i = 0; i < data.length; i += 2) {
  1.6942 +      Buf.writeUint16(data.charCodeAt(i + 1));
  1.6943 +      Buf.writeUint16(data.charCodeAt(i));
  1.6944 +    }
  1.6945 +  },
  1.6946 +
  1.6947 +  /**
  1.6948 +   * Read user data, convert to septets, look up relevant characters in a
  1.6949 +   * 7-bit alphabet, and construct string.
  1.6950 +   *
  1.6951 +   * @param length
  1.6952 +   *        Number of septets to read (*not* octets)
  1.6953 +   * @param paddingBits
  1.6954 +   *        Number of padding bits in the first byte of user data.
  1.6955 +   * @param langIndex
  1.6956 +   *        Table index used for normal 7-bit encoded character lookup.
  1.6957 +   * @param langShiftIndex
  1.6958 +   *        Table index used for escaped 7-bit encoded character lookup.
  1.6959 +   *
  1.6960 +   * @return a string.
  1.6961 +   */
  1.6962 +  readSeptetsToString: function(length, paddingBits, langIndex, langShiftIndex) {
  1.6963 +    let ret = "";
  1.6964 +    let byteLength = Math.ceil((length * 7 + paddingBits) / 8);
  1.6965 +
  1.6966 +    /**
  1.6967 +     * |<-                    last byte in header                    ->|
  1.6968 +     * |<-           incompleteBits          ->|<- last header septet->|
  1.6969 +     * +===7===|===6===|===5===|===4===|===3===|===2===|===1===|===0===|
  1.6970 +     *
  1.6971 +     * |<-                   1st byte in user data                   ->|
  1.6972 +     * |<-               data septet 1               ->|<-paddingBits->|
  1.6973 +     * +===7===|===6===|===5===|===4===|===3===|===2===|===1===|===0===|
  1.6974 +     *
  1.6975 +     * |<-                   2nd byte in user data                   ->|
  1.6976 +     * |<-                   data spetet 2                   ->|<-ds1->|
  1.6977 +     * +===7===|===6===|===5===|===4===|===3===|===2===|===1===|===0===|
  1.6978 +     */
  1.6979 +    let data = 0;
  1.6980 +    let dataBits = 0;
  1.6981 +    if (paddingBits) {
  1.6982 +      data = this.readHexOctet() >> paddingBits;
  1.6983 +      dataBits = 8 - paddingBits;
  1.6984 +      --byteLength;
  1.6985 +    }
  1.6986 +
  1.6987 +    let escapeFound = false;
  1.6988 +    const langTable = PDU_NL_LOCKING_SHIFT_TABLES[langIndex];
  1.6989 +    const langShiftTable = PDU_NL_SINGLE_SHIFT_TABLES[langShiftIndex];
  1.6990 +    do {
  1.6991 +      // Read as much as fits in 32bit word
  1.6992 +      let bytesToRead = Math.min(byteLength, dataBits ? 3 : 4);
  1.6993 +      for (let i = 0; i < bytesToRead; i++) {
  1.6994 +        data |= this.readHexOctet() << dataBits;
  1.6995 +        dataBits += 8;
  1.6996 +        --byteLength;
  1.6997 +      }
  1.6998 +
  1.6999 +      // Consume available full septets
  1.7000 +      for (; dataBits >= 7; dataBits -= 7) {
  1.7001 +        let septet = data & 0x7F;
  1.7002 +        data >>>= 7;
  1.7003 +
  1.7004 +        if (escapeFound) {
  1.7005 +          escapeFound = false;
  1.7006 +          if (septet == PDU_NL_EXTENDED_ESCAPE) {
  1.7007 +            // According to 3GPP TS 23.038, section 6.2.1.1, NOTE 1, "On
  1.7008 +            // receipt of this code, a receiving entity shall display a space
  1.7009 +            // until another extensiion table is defined."
  1.7010 +            ret += " ";
  1.7011 +          } else if (septet == PDU_NL_RESERVED_CONTROL) {
  1.7012 +            // According to 3GPP TS 23.038 B.2, "This code represents a control
  1.7013 +            // character and therefore must not be used for language specific
  1.7014 +            // characters."
  1.7015 +            ret += " ";
  1.7016 +          } else {
  1.7017 +            ret += langShiftTable[septet];
  1.7018 +          }
  1.7019 +        } else if (septet == PDU_NL_EXTENDED_ESCAPE) {
  1.7020 +          escapeFound = true;
  1.7021 +
  1.7022 +          // <escape> is not an effective character
  1.7023 +          --length;
  1.7024 +        } else {
  1.7025 +          ret += langTable[septet];
  1.7026 +        }
  1.7027 +      }
  1.7028 +    } while (byteLength);
  1.7029 +
  1.7030 +    if (ret.length != length) {
  1.7031 +      /**
  1.7032 +       * If num of effective characters does not equal to the length of read
  1.7033 +       * string, cut the tail off. This happens when the last octet of user
  1.7034 +       * data has following layout:
  1.7035 +       *
  1.7036 +       * |<-              penultimate octet in user data               ->|
  1.7037 +       * |<-               data septet N               ->|<-   dsN-1   ->|
  1.7038 +       * +===7===|===6===|===5===|===4===|===3===|===2===|===1===|===0===|
  1.7039 +       *
  1.7040 +       * |<-                  last octet in user data                  ->|
  1.7041 +       * |<-                       fill bits                   ->|<-dsN->|
  1.7042 +       * +===7===|===6===|===5===|===4===|===3===|===2===|===1===|===0===|
  1.7043 +       *
  1.7044 +       * The fill bits in the last octet may happen to form a full septet and
  1.7045 +       * be appended at the end of result string.
  1.7046 +       */
  1.7047 +      ret = ret.slice(0, length);
  1.7048 +    }
  1.7049 +    return ret;
  1.7050 +  },
  1.7051 +
  1.7052 +  writeStringAsSeptets: function(message, paddingBits, langIndex, langShiftIndex) {
  1.7053 +    const langTable = PDU_NL_LOCKING_SHIFT_TABLES[langIndex];
  1.7054 +    const langShiftTable = PDU_NL_SINGLE_SHIFT_TABLES[langShiftIndex];
  1.7055 +
  1.7056 +    let dataBits = paddingBits;
  1.7057 +    let data = 0;
  1.7058 +    for (let i = 0; i < message.length; i++) {
  1.7059 +      let c = message.charAt(i);
  1.7060 +      let septet = langTable.indexOf(c);
  1.7061 +      if (septet == PDU_NL_EXTENDED_ESCAPE) {
  1.7062 +        continue;
  1.7063 +      }
  1.7064 +
  1.7065 +      if (septet >= 0) {
  1.7066 +        data |= septet << dataBits;
  1.7067 +        dataBits += 7;
  1.7068 +      } else {
  1.7069 +        septet = langShiftTable.indexOf(c);
  1.7070 +        if (septet == -1) {
  1.7071 +          throw new Error("'" + c + "' is not in 7 bit alphabet "
  1.7072 +                          + langIndex + ":" + langShiftIndex + "!");
  1.7073 +        }
  1.7074 +
  1.7075 +        if (septet == PDU_NL_RESERVED_CONTROL) {
  1.7076 +          continue;
  1.7077 +        }
  1.7078 +
  1.7079 +        data |= PDU_NL_EXTENDED_ESCAPE << dataBits;
  1.7080 +        dataBits += 7;
  1.7081 +        data |= septet << dataBits;
  1.7082 +        dataBits += 7;
  1.7083 +      }
  1.7084 +
  1.7085 +      for (; dataBits >= 8; dataBits -= 8) {
  1.7086 +        this.writeHexOctet(data & 0xFF);
  1.7087 +        data >>>= 8;
  1.7088 +      }
  1.7089 +    }
  1.7090 +
  1.7091 +    if (dataBits !== 0) {
  1.7092 +      this.writeHexOctet(data & 0xFF);
  1.7093 +    }
  1.7094 +  },
  1.7095 +
  1.7096 +  /**
  1.7097 +   * Read user data and decode as a UCS2 string.
  1.7098 +   *
  1.7099 +   * @param numOctets
  1.7100 +   *        Number of octets to be read as UCS2 string.
  1.7101 +   *
  1.7102 +   * @return a string.
  1.7103 +   */
  1.7104 +  readUCS2String: function(numOctets) {
  1.7105 +    let str = "";
  1.7106 +    let length = numOctets / 2;
  1.7107 +    for (let i = 0; i < length; ++i) {
  1.7108 +      let code = (this.readHexOctet() << 8) | this.readHexOctet();
  1.7109 +      str += String.fromCharCode(code);
  1.7110 +    }
  1.7111 +
  1.7112 +    if (DEBUG) this.context.debug("Read UCS2 string: " + str);
  1.7113 +
  1.7114 +    return str;
  1.7115 +  },
  1.7116 +
  1.7117 +  /**
  1.7118 +   * Write user data as a UCS2 string.
  1.7119 +   *
  1.7120 +   * @param message
  1.7121 +   *        Message string to encode as UCS2 in hex-encoded octets.
  1.7122 +   */
  1.7123 +  writeUCS2String: function(message) {
  1.7124 +    for (let i = 0; i < message.length; ++i) {
  1.7125 +      let code = message.charCodeAt(i);
  1.7126 +      this.writeHexOctet((code >> 8) & 0xFF);
  1.7127 +      this.writeHexOctet(code & 0xFF);
  1.7128 +    }
  1.7129 +  },
  1.7130 +
  1.7131 +  /**
  1.7132 +   * Read 1 + UDHL octets and construct user data header.
  1.7133 +   *
  1.7134 +   * @param msg
  1.7135 +   *        message object for output.
  1.7136 +   *
  1.7137 +   * @see 3GPP TS 23.040 9.2.3.24
  1.7138 +   */
  1.7139 +  readUserDataHeader: function(msg) {
  1.7140 +    /**
  1.7141 +     * A header object with properties contained in received message.
  1.7142 +     * The properties set include:
  1.7143 +     *
  1.7144 +     * length: totoal length of the header, default 0.
  1.7145 +     * langIndex: used locking shift table index, default
  1.7146 +     * PDU_NL_IDENTIFIER_DEFAULT.
  1.7147 +     * langShiftIndex: used locking shift table index, default
  1.7148 +     * PDU_NL_IDENTIFIER_DEFAULT.
  1.7149 +     *
  1.7150 +     */
  1.7151 +    let header = {
  1.7152 +      length: 0,
  1.7153 +      langIndex: PDU_NL_IDENTIFIER_DEFAULT,
  1.7154 +      langShiftIndex: PDU_NL_IDENTIFIER_DEFAULT
  1.7155 +    };
  1.7156 +
  1.7157 +    header.length = this.readHexOctet();
  1.7158 +    if (DEBUG) this.context.debug("Read UDH length: " + header.length);
  1.7159 +
  1.7160 +    let dataAvailable = header.length;
  1.7161 +    while (dataAvailable >= 2) {
  1.7162 +      let id = this.readHexOctet();
  1.7163 +      let length = this.readHexOctet();
  1.7164 +      if (DEBUG) this.context.debug("Read UDH id: " + id + ", length: " + length);
  1.7165 +
  1.7166 +      dataAvailable -= 2;
  1.7167 +
  1.7168 +      switch (id) {
  1.7169 +        case PDU_IEI_CONCATENATED_SHORT_MESSAGES_8BIT: {
  1.7170 +          let ref = this.readHexOctet();
  1.7171 +          let max = this.readHexOctet();
  1.7172 +          let seq = this.readHexOctet();
  1.7173 +          dataAvailable -= 3;
  1.7174 +          if (max && seq && (seq <= max)) {
  1.7175 +            header.segmentRef = ref;
  1.7176 +            header.segmentMaxSeq = max;
  1.7177 +            header.segmentSeq = seq;
  1.7178 +          }
  1.7179 +          break;
  1.7180 +        }
  1.7181 +        case PDU_IEI_APPLICATION_PORT_ADDRESSING_SCHEME_8BIT: {
  1.7182 +          let dstp = this.readHexOctet();
  1.7183 +          let orip = this.readHexOctet();
  1.7184 +          dataAvailable -= 2;
  1.7185 +          if ((dstp < PDU_APA_RESERVED_8BIT_PORTS)
  1.7186 +              || (orip < PDU_APA_RESERVED_8BIT_PORTS)) {
  1.7187 +            // 3GPP TS 23.040 clause 9.2.3.24.3: "A receiving entity shall
  1.7188 +            // ignore any information element where the value of the
  1.7189 +            // Information-Element-Data is Reserved or not supported"
  1.7190 +            break;
  1.7191 +          }
  1.7192 +          header.destinationPort = dstp;
  1.7193 +          header.originatorPort = orip;
  1.7194 +          break;
  1.7195 +        }
  1.7196 +        case PDU_IEI_APPLICATION_PORT_ADDRESSING_SCHEME_16BIT: {
  1.7197 +          let dstp = (this.readHexOctet() << 8) | this.readHexOctet();
  1.7198 +          let orip = (this.readHexOctet() << 8) | this.readHexOctet();
  1.7199 +          dataAvailable -= 4;
  1.7200 +          // 3GPP TS 23.040 clause 9.2.3.24.4: "A receiving entity shall
  1.7201 +          // ignore any information element where the value of the
  1.7202 +          // Information-Element-Data is Reserved or not supported"
  1.7203 +          if ((dstp < PDU_APA_VALID_16BIT_PORTS)
  1.7204 +              && (orip < PDU_APA_VALID_16BIT_PORTS)) {
  1.7205 +            header.destinationPort = dstp;
  1.7206 +            header.originatorPort = orip;
  1.7207 +          }
  1.7208 +          break;
  1.7209 +        }
  1.7210 +        case PDU_IEI_CONCATENATED_SHORT_MESSAGES_16BIT: {
  1.7211 +          let ref = (this.readHexOctet() << 8) | this.readHexOctet();
  1.7212 +          let max = this.readHexOctet();
  1.7213 +          let seq = this.readHexOctet();
  1.7214 +          dataAvailable -= 4;
  1.7215 +          if (max && seq && (seq <= max)) {
  1.7216 +            header.segmentRef = ref;
  1.7217 +            header.segmentMaxSeq = max;
  1.7218 +            header.segmentSeq = seq;
  1.7219 +          }
  1.7220 +          break;
  1.7221 +        }
  1.7222 +        case PDU_IEI_NATIONAL_LANGUAGE_SINGLE_SHIFT:
  1.7223 +          let langShiftIndex = this.readHexOctet();
  1.7224 +          --dataAvailable;
  1.7225 +          if (langShiftIndex < PDU_NL_SINGLE_SHIFT_TABLES.length) {
  1.7226 +            header.langShiftIndex = langShiftIndex;
  1.7227 +          }
  1.7228 +          break;
  1.7229 +        case PDU_IEI_NATIONAL_LANGUAGE_LOCKING_SHIFT:
  1.7230 +          let langIndex = this.readHexOctet();
  1.7231 +          --dataAvailable;
  1.7232 +          if (langIndex < PDU_NL_LOCKING_SHIFT_TABLES.length) {
  1.7233 +            header.langIndex = langIndex;
  1.7234 +          }
  1.7235 +          break;
  1.7236 +        case PDU_IEI_SPECIAL_SMS_MESSAGE_INDICATION:
  1.7237 +          let msgInd = this.readHexOctet() & 0xFF;
  1.7238 +          let msgCount = this.readHexOctet();
  1.7239 +          dataAvailable -= 2;
  1.7240 +
  1.7241 +
  1.7242 +          /*
  1.7243 +           * TS 23.040 V6.8.1 Sec 9.2.3.24.2
  1.7244 +           * bits 1 0   : basic message indication type
  1.7245 +           * bits 4 3 2 : extended message indication type
  1.7246 +           * bits 6 5   : Profile id
  1.7247 +           * bit  7     : storage type
  1.7248 +           */
  1.7249 +          let storeType = msgInd & PDU_MWI_STORE_TYPE_BIT;
  1.7250 +          let mwi = msg.mwi;
  1.7251 +          if (!mwi) {
  1.7252 +            mwi = msg.mwi = {};
  1.7253 +          }
  1.7254 +
  1.7255 +          if (storeType == PDU_MWI_STORE_TYPE_STORE) {
  1.7256 +            // Store message because TP_UDH indicates so, note this may override
  1.7257 +            // the setting in DCS, but that is expected
  1.7258 +            mwi.discard = false;
  1.7259 +          } else if (mwi.discard === undefined) {
  1.7260 +            // storeType == PDU_MWI_STORE_TYPE_DISCARD
  1.7261 +            // only override mwi.discard here if it hasn't already been set
  1.7262 +            mwi.discard = true;
  1.7263 +          }
  1.7264 +
  1.7265 +          mwi.msgCount = msgCount & 0xFF;
  1.7266 +          mwi.active = mwi.msgCount > 0;
  1.7267 +
  1.7268 +          if (DEBUG) {
  1.7269 +            this.context.debug("MWI in TP_UDH received: " + JSON.stringify(mwi));
  1.7270 +          }
  1.7271 +
  1.7272 +          break;
  1.7273 +        default:
  1.7274 +          if (DEBUG) {
  1.7275 +            this.context.debug("readUserDataHeader: unsupported IEI(" + id +
  1.7276 +                               "), " + length + " bytes.");
  1.7277 +          }
  1.7278 +
  1.7279 +          // Read out unsupported data
  1.7280 +          if (length) {
  1.7281 +            let octets;
  1.7282 +            if (DEBUG) octets = new Uint8Array(length);
  1.7283 +
  1.7284 +            for (let i = 0; i < length; i++) {
  1.7285 +              let octet = this.readHexOctet();
  1.7286 +              if (DEBUG) octets[i] = octet;
  1.7287 +            }
  1.7288 +            dataAvailable -= length;
  1.7289 +
  1.7290 +            if (DEBUG) {
  1.7291 +              this.context.debug("readUserDataHeader: " + Array.slice(octets));
  1.7292 +            }
  1.7293 +          }
  1.7294 +          break;
  1.7295 +      }
  1.7296 +    }
  1.7297 +
  1.7298 +    if (dataAvailable !== 0) {
  1.7299 +      throw new Error("Illegal user data header found!");
  1.7300 +    }
  1.7301 +
  1.7302 +    msg.header = header;
  1.7303 +  },
  1.7304 +
  1.7305 +  /**
  1.7306 +   * Write out user data header.
  1.7307 +   *
  1.7308 +   * @param options
  1.7309 +   *        Options containing information for user data header write-out. The
  1.7310 +   *        `userDataHeaderLength` property must be correctly pre-calculated.
  1.7311 +   */
  1.7312 +  writeUserDataHeader: function(options) {
  1.7313 +    this.writeHexOctet(options.userDataHeaderLength);
  1.7314 +
  1.7315 +    if (options.segmentMaxSeq > 1) {
  1.7316 +      if (options.segmentRef16Bit) {
  1.7317 +        this.writeHexOctet(PDU_IEI_CONCATENATED_SHORT_MESSAGES_16BIT);
  1.7318 +        this.writeHexOctet(4);
  1.7319 +        this.writeHexOctet((options.segmentRef >> 8) & 0xFF);
  1.7320 +      } else {
  1.7321 +        this.writeHexOctet(PDU_IEI_CONCATENATED_SHORT_MESSAGES_8BIT);
  1.7322 +        this.writeHexOctet(3);
  1.7323 +      }
  1.7324 +      this.writeHexOctet(options.segmentRef & 0xFF);
  1.7325 +      this.writeHexOctet(options.segmentMaxSeq & 0xFF);
  1.7326 +      this.writeHexOctet(options.segmentSeq & 0xFF);
  1.7327 +    }
  1.7328 +
  1.7329 +    if (options.dcs == PDU_DCS_MSG_CODING_7BITS_ALPHABET) {
  1.7330 +      if (options.langIndex != PDU_NL_IDENTIFIER_DEFAULT) {
  1.7331 +        this.writeHexOctet(PDU_IEI_NATIONAL_LANGUAGE_LOCKING_SHIFT);
  1.7332 +        this.writeHexOctet(1);
  1.7333 +        this.writeHexOctet(options.langIndex);
  1.7334 +      }
  1.7335 +
  1.7336 +      if (options.langShiftIndex != PDU_NL_IDENTIFIER_DEFAULT) {
  1.7337 +        this.writeHexOctet(PDU_IEI_NATIONAL_LANGUAGE_SINGLE_SHIFT);
  1.7338 +        this.writeHexOctet(1);
  1.7339 +        this.writeHexOctet(options.langShiftIndex);
  1.7340 +      }
  1.7341 +    }
  1.7342 +  },
  1.7343 +
  1.7344 +  /**
  1.7345 +   * Read SM-TL Address.
  1.7346 +   *
  1.7347 +   * @param len
  1.7348 +   *        Length of useful semi-octets within the Address-Value field. For
  1.7349 +   *        example, the lenth of "12345" should be 5, and 4 for "1234".
  1.7350 +   *
  1.7351 +   * @see 3GPP TS 23.040 9.1.2.5
  1.7352 +   */
  1.7353 +  readAddress: function(len) {
  1.7354 +    // Address Length
  1.7355 +    if (!len || (len < 0)) {
  1.7356 +      if (DEBUG) {
  1.7357 +        this.context.debug("PDU error: invalid sender address length: " + len);
  1.7358 +      }
  1.7359 +      return null;
  1.7360 +    }
  1.7361 +    if (len % 2 == 1) {
  1.7362 +      len += 1;
  1.7363 +    }
  1.7364 +    if (DEBUG) this.context.debug("PDU: Going to read address: " + len);
  1.7365 +
  1.7366 +    // Type-of-Address
  1.7367 +    let toa = this.readHexOctet();
  1.7368 +    let addr = "";
  1.7369 +
  1.7370 +    if ((toa & 0xF0) == PDU_TOA_ALPHANUMERIC) {
  1.7371 +      addr = this.readSeptetsToString(Math.floor(len * 4 / 7), 0,
  1.7372 +          PDU_NL_IDENTIFIER_DEFAULT , PDU_NL_IDENTIFIER_DEFAULT );
  1.7373 +      return addr;
  1.7374 +    }
  1.7375 +    addr = this.readSwappedNibbleBcdString(len / 2);
  1.7376 +    if (addr.length <= 0) {
  1.7377 +      if (DEBUG) this.context.debug("PDU error: no number provided");
  1.7378 +      return null;
  1.7379 +    }
  1.7380 +    if ((toa & 0xF0) == (PDU_TOA_INTERNATIONAL)) {
  1.7381 +      addr = '+' + addr;
  1.7382 +    }
  1.7383 +
  1.7384 +    return addr;
  1.7385 +  },
  1.7386 +
  1.7387 +  /**
  1.7388 +   * Read TP-Protocol-Indicator(TP-PID).
  1.7389 +   *
  1.7390 +   * @param msg
  1.7391 +   *        message object for output.
  1.7392 +   *
  1.7393 +   * @see 3GPP TS 23.040 9.2.3.9
  1.7394 +   */
  1.7395 +  readProtocolIndicator: function(msg) {
  1.7396 +    // `The MS shall interpret reserved, obsolete, or unsupported values as the
  1.7397 +    // value 00000000 but shall store them exactly as received.`
  1.7398 +    msg.pid = this.readHexOctet();
  1.7399 +
  1.7400 +    msg.epid = msg.pid;
  1.7401 +    switch (msg.epid & 0xC0) {
  1.7402 +      case 0x40:
  1.7403 +        // Bit 7..0 = 01xxxxxx
  1.7404 +        switch (msg.epid) {
  1.7405 +          case PDU_PID_SHORT_MESSAGE_TYPE_0:
  1.7406 +          case PDU_PID_ANSI_136_R_DATA:
  1.7407 +          case PDU_PID_USIM_DATA_DOWNLOAD:
  1.7408 +            return;
  1.7409 +        }
  1.7410 +        break;
  1.7411 +    }
  1.7412 +
  1.7413 +    msg.epid = PDU_PID_DEFAULT;
  1.7414 +  },
  1.7415 +
  1.7416 +  /**
  1.7417 +   * Read TP-Data-Coding-Scheme(TP-DCS)
  1.7418 +   *
  1.7419 +   * @param msg
  1.7420 +   *        message object for output.
  1.7421 +   *
  1.7422 +   * @see 3GPP TS 23.040 9.2.3.10, 3GPP TS 23.038 4.
  1.7423 +   */
  1.7424 +  readDataCodingScheme: function(msg) {
  1.7425 +    let dcs = this.readHexOctet();
  1.7426 +    if (DEBUG) this.context.debug("PDU: read SMS dcs: " + dcs);
  1.7427 +
  1.7428 +    // No message class by default.
  1.7429 +    let messageClass = PDU_DCS_MSG_CLASS_NORMAL;
  1.7430 +    // 7 bit is the default fallback encoding.
  1.7431 +    let encoding = PDU_DCS_MSG_CODING_7BITS_ALPHABET;
  1.7432 +    switch (dcs & PDU_DCS_CODING_GROUP_BITS) {
  1.7433 +      case 0x40: // bits 7..4 = 01xx
  1.7434 +      case 0x50:
  1.7435 +      case 0x60:
  1.7436 +      case 0x70:
  1.7437 +        // Bit 5..0 are coded exactly the same as Group 00xx
  1.7438 +      case 0x00: // bits 7..4 = 00xx
  1.7439 +      case 0x10:
  1.7440 +      case 0x20:
  1.7441 +      case 0x30:
  1.7442 +        if (dcs & 0x10) {
  1.7443 +          messageClass = dcs & PDU_DCS_MSG_CLASS_BITS;
  1.7444 +        }
  1.7445 +        switch (dcs & 0x0C) {
  1.7446 +          case 0x4:
  1.7447 +            encoding = PDU_DCS_MSG_CODING_8BITS_ALPHABET;
  1.7448 +            break;
  1.7449 +          case 0x8:
  1.7450 +            encoding = PDU_DCS_MSG_CODING_16BITS_ALPHABET;
  1.7451 +            break;
  1.7452 +        }
  1.7453 +        break;
  1.7454 +
  1.7455 +      case 0xE0: // bits 7..4 = 1110
  1.7456 +        encoding = PDU_DCS_MSG_CODING_16BITS_ALPHABET;
  1.7457 +        // Bit 3..0 are coded exactly the same as Message Waiting Indication
  1.7458 +        // Group 1101.
  1.7459 +        // Fall through.
  1.7460 +      case 0xC0: // bits 7..4 = 1100
  1.7461 +      case 0xD0: // bits 7..4 = 1101
  1.7462 +        // Indiciates voicemail indicator set or clear
  1.7463 +        let active = (dcs & PDU_DCS_MWI_ACTIVE_BITS) == PDU_DCS_MWI_ACTIVE_VALUE;
  1.7464 +
  1.7465 +        // If TP-UDH is present, these values will be overwritten
  1.7466 +        switch (dcs & PDU_DCS_MWI_TYPE_BITS) {
  1.7467 +          case PDU_DCS_MWI_TYPE_VOICEMAIL:
  1.7468 +            let mwi = msg.mwi;
  1.7469 +            if (!mwi) {
  1.7470 +              mwi = msg.mwi = {};
  1.7471 +            }
  1.7472 +
  1.7473 +            mwi.active = active;
  1.7474 +            mwi.discard = (dcs & PDU_DCS_CODING_GROUP_BITS) == 0xC0;
  1.7475 +            mwi.msgCount = active ? GECKO_VOICEMAIL_MESSAGE_COUNT_UNKNOWN : 0;
  1.7476 +
  1.7477 +            if (DEBUG) {
  1.7478 +              this.context.debug("MWI in DCS received for voicemail: " +
  1.7479 +                                 JSON.stringify(mwi));
  1.7480 +            }
  1.7481 +            break;
  1.7482 +          case PDU_DCS_MWI_TYPE_FAX:
  1.7483 +            if (DEBUG) this.context.debug("MWI in DCS received for fax");
  1.7484 +            break;
  1.7485 +          case PDU_DCS_MWI_TYPE_EMAIL:
  1.7486 +            if (DEBUG) this.context.debug("MWI in DCS received for email");
  1.7487 +            break;
  1.7488 +          default:
  1.7489 +            if (DEBUG) this.context.debug("MWI in DCS received for \"other\"");
  1.7490 +            break;
  1.7491 +        }
  1.7492 +        break;
  1.7493 +
  1.7494 +      case 0xF0: // bits 7..4 = 1111
  1.7495 +        if (dcs & 0x04) {
  1.7496 +          encoding = PDU_DCS_MSG_CODING_8BITS_ALPHABET;
  1.7497 +        }
  1.7498 +        messageClass = dcs & PDU_DCS_MSG_CLASS_BITS;
  1.7499 +        break;
  1.7500 +
  1.7501 +      default:
  1.7502 +        // Falling back to default encoding.
  1.7503 +        break;
  1.7504 +    }
  1.7505 +
  1.7506 +    msg.dcs = dcs;
  1.7507 +    msg.encoding = encoding;
  1.7508 +    msg.messageClass = GECKO_SMS_MESSAGE_CLASSES[messageClass];
  1.7509 +
  1.7510 +    if (DEBUG) this.context.debug("PDU: message encoding is " + encoding + " bit.");
  1.7511 +  },
  1.7512 +
  1.7513 +  /**
  1.7514 +   * Read GSM TP-Service-Centre-Time-Stamp(TP-SCTS).
  1.7515 +   *
  1.7516 +   * @see 3GPP TS 23.040 9.2.3.11
  1.7517 +   */
  1.7518 +  readTimestamp: function() {
  1.7519 +    let year   = this.readSwappedNibbleBcdNum(1) + PDU_TIMESTAMP_YEAR_OFFSET;
  1.7520 +    let month  = this.readSwappedNibbleBcdNum(1) - 1;
  1.7521 +    let day    = this.readSwappedNibbleBcdNum(1);
  1.7522 +    let hour   = this.readSwappedNibbleBcdNum(1);
  1.7523 +    let minute = this.readSwappedNibbleBcdNum(1);
  1.7524 +    let second = this.readSwappedNibbleBcdNum(1);
  1.7525 +    let timestamp = Date.UTC(year, month, day, hour, minute, second);
  1.7526 +
  1.7527 +    // If the most significant bit of the least significant nibble is 1,
  1.7528 +    // the timezone offset is negative (fourth bit from the right => 0x08):
  1.7529 +    //   localtime = UTC + tzOffset
  1.7530 +    // therefore
  1.7531 +    //   UTC = localtime - tzOffset
  1.7532 +    let tzOctet = this.readHexOctet();
  1.7533 +    let tzOffset = this.octetToBCD(tzOctet & ~0x08) * 15 * 60 * 1000;
  1.7534 +    tzOffset = (tzOctet & 0x08) ? -tzOffset : tzOffset;
  1.7535 +    timestamp -= tzOffset;
  1.7536 +
  1.7537 +    return timestamp;
  1.7538 +  },
  1.7539 +
  1.7540 +  /**
  1.7541 +   * Write GSM TP-Service-Centre-Time-Stamp(TP-SCTS).
  1.7542 +   *
  1.7543 +   * @see 3GPP TS 23.040 9.2.3.11
  1.7544 +   */
  1.7545 +  writeTimestamp: function(date) {
  1.7546 +    this.writeSwappedNibbleBCDNum(date.getFullYear() - PDU_TIMESTAMP_YEAR_OFFSET);
  1.7547 +
  1.7548 +    // The value returned by getMonth() is an integer between 0 and 11.
  1.7549 +    // 0 is corresponds to January, 1 to February, and so on.
  1.7550 +    this.writeSwappedNibbleBCDNum(date.getMonth() + 1);
  1.7551 +    this.writeSwappedNibbleBCDNum(date.getDate());
  1.7552 +    this.writeSwappedNibbleBCDNum(date.getHours());
  1.7553 +    this.writeSwappedNibbleBCDNum(date.getMinutes());
  1.7554 +    this.writeSwappedNibbleBCDNum(date.getSeconds());
  1.7555 +
  1.7556 +    // the value returned by getTimezoneOffset() is the difference,
  1.7557 +    // in minutes, between UTC and local time.
  1.7558 +    // For example, if your time zone is UTC+10 (Australian Eastern Standard Time),
  1.7559 +    // -600 will be returned.
  1.7560 +    // In TS 23.040 9.2.3.11, the Time Zone field of TP-SCTS indicates
  1.7561 +    // the different between the local time and GMT.
  1.7562 +    // And expressed in quarters of an hours. (so need to divid by 15)
  1.7563 +    let zone = date.getTimezoneOffset() / 15;
  1.7564 +    let octet = this.BCDToOctet(zone);
  1.7565 +
  1.7566 +    // the bit3 of the Time Zone field represents the algebraic sign.
  1.7567 +    // (0: positive, 1: negative).
  1.7568 +    // For example, if the time zone is -0800 GMT,
  1.7569 +    // 480 will be returned by getTimezoneOffset().
  1.7570 +    // In this case, need to mark sign bit as 1. => 0x08
  1.7571 +    if (zone > 0) {
  1.7572 +      octet = octet | 0x08;
  1.7573 +    }
  1.7574 +    this.writeHexOctet(octet);
  1.7575 +  },
  1.7576 +
  1.7577 +  /**
  1.7578 +   * User data can be 7 bit (default alphabet) data, 8 bit data, or 16 bit
  1.7579 +   * (UCS2) data.
  1.7580 +   *
  1.7581 +   * @param msg
  1.7582 +   *        message object for output.
  1.7583 +   * @param length
  1.7584 +   *        length of user data to read in octets.
  1.7585 +   */
  1.7586 +  readUserData: function(msg, length) {
  1.7587 +    if (DEBUG) {
  1.7588 +      this.context.debug("Reading " + length + " bytes of user data.");
  1.7589 +    }
  1.7590 +
  1.7591 +    let paddingBits = 0;
  1.7592 +    if (msg.udhi) {
  1.7593 +      this.readUserDataHeader(msg);
  1.7594 +
  1.7595 +      if (msg.encoding == PDU_DCS_MSG_CODING_7BITS_ALPHABET) {
  1.7596 +        let headerBits = (msg.header.length + 1) * 8;
  1.7597 +        let headerSeptets = Math.ceil(headerBits / 7);
  1.7598 +
  1.7599 +        length -= headerSeptets;
  1.7600 +        paddingBits = headerSeptets * 7 - headerBits;
  1.7601 +      } else {
  1.7602 +        length -= (msg.header.length + 1);
  1.7603 +      }
  1.7604 +    }
  1.7605 +
  1.7606 +    if (DEBUG) {
  1.7607 +      this.context.debug("After header, " + length + " septets left of user data");
  1.7608 +    }
  1.7609 +
  1.7610 +    msg.body = null;
  1.7611 +    msg.data = null;
  1.7612 +    switch (msg.encoding) {
  1.7613 +      case PDU_DCS_MSG_CODING_7BITS_ALPHABET:
  1.7614 +        // 7 bit encoding allows 140 octets, which means 160 characters
  1.7615 +        // ((140x8) / 7 = 160 chars)
  1.7616 +        if (length > PDU_MAX_USER_DATA_7BIT) {
  1.7617 +          if (DEBUG) {
  1.7618 +            this.context.debug("PDU error: user data is too long: " + length);
  1.7619 +          }
  1.7620 +          break;
  1.7621 +        }
  1.7622 +
  1.7623 +        let langIndex = msg.udhi ? msg.header.langIndex : PDU_NL_IDENTIFIER_DEFAULT;
  1.7624 +        let langShiftIndex = msg.udhi ? msg.header.langShiftIndex : PDU_NL_IDENTIFIER_DEFAULT;
  1.7625 +        msg.body = this.readSeptetsToString(length, paddingBits, langIndex,
  1.7626 +                                            langShiftIndex);
  1.7627 +        break;
  1.7628 +      case PDU_DCS_MSG_CODING_8BITS_ALPHABET:
  1.7629 +        msg.data = this.readHexOctetArray(length);
  1.7630 +        break;
  1.7631 +      case PDU_DCS_MSG_CODING_16BITS_ALPHABET:
  1.7632 +        msg.body = this.readUCS2String(length);
  1.7633 +        break;
  1.7634 +    }
  1.7635 +  },
  1.7636 +
  1.7637 +  /**
  1.7638 +   * Read extra parameters if TP-PI is set.
  1.7639 +   *
  1.7640 +   * @param msg
  1.7641 +   *        message object for output.
  1.7642 +   */
  1.7643 +  readExtraParams: function(msg) {
  1.7644 +    // Because each PDU octet is converted to two UCS2 char2, we should always
  1.7645 +    // get even messageStringLength in this#_processReceivedSms(). So, we'll
  1.7646 +    // always need two delimitors at the end.
  1.7647 +    if (this.context.Buf.getReadAvailable() <= 4) {
  1.7648 +      return;
  1.7649 +    }
  1.7650 +
  1.7651 +    // TP-Parameter-Indicator
  1.7652 +    let pi;
  1.7653 +    do {
  1.7654 +      // `The most significant bit in octet 1 and any other TP-PI octets which
  1.7655 +      // may be added later is reserved as an extension bit which when set to a
  1.7656 +      // 1 shall indicate that another TP-PI octet follows immediately
  1.7657 +      // afterwards.` ~ 3GPP TS 23.040 9.2.3.27
  1.7658 +      pi = this.readHexOctet();
  1.7659 +    } while (pi & PDU_PI_EXTENSION);
  1.7660 +
  1.7661 +    // `If the TP-UDL bit is set to "1" but the TP-DCS bit is set to "0" then
  1.7662 +    // the receiving entity shall for TP-DCS assume a value of 0x00, i.e. the
  1.7663 +    // 7bit default alphabet.` ~ 3GPP 23.040 9.2.3.27
  1.7664 +    msg.dcs = 0;
  1.7665 +    msg.encoding = PDU_DCS_MSG_CODING_7BITS_ALPHABET;
  1.7666 +
  1.7667 +    // TP-Protocol-Identifier
  1.7668 +    if (pi & PDU_PI_PROTOCOL_IDENTIFIER) {
  1.7669 +      this.readProtocolIndicator(msg);
  1.7670 +    }
  1.7671 +    // TP-Data-Coding-Scheme
  1.7672 +    if (pi & PDU_PI_DATA_CODING_SCHEME) {
  1.7673 +      this.readDataCodingScheme(msg);
  1.7674 +    }
  1.7675 +    // TP-User-Data-Length
  1.7676 +    if (pi & PDU_PI_USER_DATA_LENGTH) {
  1.7677 +      let userDataLength = this.readHexOctet();
  1.7678 +      this.readUserData(msg, userDataLength);
  1.7679 +    }
  1.7680 +  },
  1.7681 +
  1.7682 +  /**
  1.7683 +   * Read and decode a PDU-encoded message from the stream.
  1.7684 +   *
  1.7685 +   * TODO: add some basic sanity checks like:
  1.7686 +   * - do we have the minimum number of chars available
  1.7687 +   */
  1.7688 +  readMessage: function() {
  1.7689 +    // An empty message object. This gets filled below and then returned.
  1.7690 +    let msg = {
  1.7691 +      // D:DELIVER, DR:DELIVER-REPORT, S:SUBMIT, SR:SUBMIT-REPORT,
  1.7692 +      // ST:STATUS-REPORT, C:COMMAND
  1.7693 +      // M:Mandatory, O:Optional, X:Unavailable
  1.7694 +      //                          D  DR S  SR ST C
  1.7695 +      SMSC:              null, // M  M  M  M  M  M
  1.7696 +      mti:               null, // M  M  M  M  M  M
  1.7697 +      udhi:              null, // M  M  O  M  M  M
  1.7698 +      sender:            null, // M  X  X  X  X  X
  1.7699 +      recipient:         null, // X  X  M  X  M  M
  1.7700 +      pid:               null, // M  O  M  O  O  M
  1.7701 +      epid:              null, // M  O  M  O  O  M
  1.7702 +      dcs:               null, // M  O  M  O  O  X
  1.7703 +      mwi:               null, // O  O  O  O  O  O
  1.7704 +      replace:          false, // O  O  O  O  O  O
  1.7705 +      header:            null, // M  M  O  M  M  M
  1.7706 +      body:              null, // M  O  M  O  O  O
  1.7707 +      data:              null, // M  O  M  O  O  O
  1.7708 +      sentTimestamp:     null, // M  X  X  X  X  X
  1.7709 +      status:            null, // X  X  X  X  M  X
  1.7710 +      scts:              null, // X  X  X  M  M  X
  1.7711 +      dt:                null, // X  X  X  X  M  X
  1.7712 +    };
  1.7713 +
  1.7714 +    // SMSC info
  1.7715 +    let smscLength = this.readHexOctet();
  1.7716 +    if (smscLength > 0) {
  1.7717 +      let smscTypeOfAddress = this.readHexOctet();
  1.7718 +      // Subtract the type-of-address octet we just read from the length.
  1.7719 +      msg.SMSC = this.readSwappedNibbleBcdString(smscLength - 1);
  1.7720 +      if ((smscTypeOfAddress >> 4) == (PDU_TOA_INTERNATIONAL >> 4)) {
  1.7721 +        msg.SMSC = '+' + msg.SMSC;
  1.7722 +      }
  1.7723 +    }
  1.7724 +
  1.7725 +    // First octet of this SMS-DELIVER or SMS-SUBMIT message
  1.7726 +    let firstOctet = this.readHexOctet();
  1.7727 +    // Message Type Indicator
  1.7728 +    msg.mti = firstOctet & 0x03;
  1.7729 +    // User data header indicator
  1.7730 +    msg.udhi = firstOctet & PDU_UDHI;
  1.7731 +
  1.7732 +    switch (msg.mti) {
  1.7733 +      case PDU_MTI_SMS_RESERVED:
  1.7734 +        // `If an MS receives a TPDU with a "Reserved" value in the TP-MTI it
  1.7735 +        // shall process the message as if it were an "SMS-DELIVER" but store
  1.7736 +        // the message exactly as received.` ~ 3GPP TS 23.040 9.2.3.1
  1.7737 +      case PDU_MTI_SMS_DELIVER:
  1.7738 +        return this.readDeliverMessage(msg);
  1.7739 +      case PDU_MTI_SMS_STATUS_REPORT:
  1.7740 +        return this.readStatusReportMessage(msg);
  1.7741 +      default:
  1.7742 +        return null;
  1.7743 +    }
  1.7744 +  },
  1.7745 +
  1.7746 +  /**
  1.7747 +   * Helper for processing received SMS parcel data.
  1.7748 +   *
  1.7749 +   * @param length
  1.7750 +   *        Length of SMS string in the incoming parcel.
  1.7751 +   *
  1.7752 +   * @return Message parsed or null for invalid message.
  1.7753 +   */
  1.7754 +  processReceivedSms: function(length) {
  1.7755 +    if (!length) {
  1.7756 +      if (DEBUG) this.context.debug("Received empty SMS!");
  1.7757 +      return [null, PDU_FCS_UNSPECIFIED];
  1.7758 +    }
  1.7759 +
  1.7760 +    let Buf = this.context.Buf;
  1.7761 +
  1.7762 +    // An SMS is a string, but we won't read it as such, so let's read the
  1.7763 +    // string length and then defer to PDU parsing helper.
  1.7764 +    let messageStringLength = Buf.readInt32();
  1.7765 +    if (DEBUG) this.context.debug("Got new SMS, length " + messageStringLength);
  1.7766 +    let message = this.readMessage();
  1.7767 +    if (DEBUG) this.context.debug("Got new SMS: " + JSON.stringify(message));
  1.7768 +
  1.7769 +    // Read string delimiters. See Buf.readString().
  1.7770 +    Buf.readStringDelimiter(length);
  1.7771 +
  1.7772 +    // Determine result
  1.7773 +    if (!message) {
  1.7774 +      return [null, PDU_FCS_UNSPECIFIED];
  1.7775 +    }
  1.7776 +
  1.7777 +    if (message.epid == PDU_PID_SHORT_MESSAGE_TYPE_0) {
  1.7778 +      // `A short message type 0 indicates that the ME must acknowledge receipt
  1.7779 +      // of the short message but shall discard its contents.` ~ 3GPP TS 23.040
  1.7780 +      // 9.2.3.9
  1.7781 +      return [null, PDU_FCS_OK];
  1.7782 +    }
  1.7783 +
  1.7784 +    if (message.messageClass == GECKO_SMS_MESSAGE_CLASSES[PDU_DCS_MSG_CLASS_2]) {
  1.7785 +      let RIL = this.context.RIL;
  1.7786 +      switch (message.epid) {
  1.7787 +        case PDU_PID_ANSI_136_R_DATA:
  1.7788 +        case PDU_PID_USIM_DATA_DOWNLOAD:
  1.7789 +          let ICCUtilsHelper = this.context.ICCUtilsHelper;
  1.7790 +          if (ICCUtilsHelper.isICCServiceAvailable("DATA_DOWNLOAD_SMS_PP")) {
  1.7791 +            // `If the service "data download via SMS Point-to-Point" is
  1.7792 +            // allocated and activated in the (U)SIM Service Table, ... then the
  1.7793 +            // ME shall pass the message transparently to the UICC using the
  1.7794 +            // ENVELOPE (SMS-PP DOWNLOAD).` ~ 3GPP TS 31.111 7.1.1.1
  1.7795 +            RIL.dataDownloadViaSMSPP(message);
  1.7796 +
  1.7797 +            // `the ME shall not display the message, or alert the user of a
  1.7798 +            // short message waiting.` ~ 3GPP TS 31.111 7.1.1.1
  1.7799 +            return [null, PDU_FCS_RESERVED];
  1.7800 +          }
  1.7801 +
  1.7802 +          // If the service "data download via SMS-PP" is not available in the
  1.7803 +          // (U)SIM Service Table, ..., then the ME shall store the message in
  1.7804 +          // EFsms in accordance with TS 31.102` ~ 3GPP TS 31.111 7.1.1.1
  1.7805 +
  1.7806 +          // Fall through.
  1.7807 +        default:
  1.7808 +          RIL.writeSmsToSIM(message);
  1.7809 +          break;
  1.7810 +      }
  1.7811 +    }
  1.7812 +
  1.7813 +    // TODO: Bug 739143: B2G SMS: Support SMS Storage Full event
  1.7814 +    if ((message.messageClass != GECKO_SMS_MESSAGE_CLASSES[PDU_DCS_MSG_CLASS_0]) && !true) {
  1.7815 +      // `When a mobile terminated message is class 0..., the MS shall display
  1.7816 +      // the message immediately and send a ACK to the SC ..., irrespective of
  1.7817 +      // whether there is memory available in the (U)SIM or ME.` ~ 3GPP 23.038
  1.7818 +      // clause 4.
  1.7819 +
  1.7820 +      if (message.messageClass == GECKO_SMS_MESSAGE_CLASSES[PDU_DCS_MSG_CLASS_2]) {
  1.7821 +        // `If all the short message storage at the MS is already in use, the
  1.7822 +        // MS shall return "memory capacity exceeded".` ~ 3GPP 23.038 clause 4.
  1.7823 +        return [null, PDU_FCS_MEMORY_CAPACITY_EXCEEDED];
  1.7824 +      }
  1.7825 +
  1.7826 +      return [null, PDU_FCS_UNSPECIFIED];
  1.7827 +    }
  1.7828 +
  1.7829 +    return [message, PDU_FCS_OK];
  1.7830 +  },
  1.7831 +
  1.7832 +  /**
  1.7833 +   * Read and decode a SMS-DELIVER PDU.
  1.7834 +   *
  1.7835 +   * @param msg
  1.7836 +   *        message object for output.
  1.7837 +   */
  1.7838 +  readDeliverMessage: function(msg) {
  1.7839 +    // - Sender Address info -
  1.7840 +    let senderAddressLength = this.readHexOctet();
  1.7841 +    msg.sender = this.readAddress(senderAddressLength);
  1.7842 +    // - TP-Protocolo-Identifier -
  1.7843 +    this.readProtocolIndicator(msg);
  1.7844 +    // - TP-Data-Coding-Scheme -
  1.7845 +    this.readDataCodingScheme(msg);
  1.7846 +    // - TP-Service-Center-Time-Stamp -
  1.7847 +    msg.sentTimestamp = this.readTimestamp();
  1.7848 +    // - TP-User-Data-Length -
  1.7849 +    let userDataLength = this.readHexOctet();
  1.7850 +
  1.7851 +    // - TP-User-Data -
  1.7852 +    if (userDataLength > 0) {
  1.7853 +      this.readUserData(msg, userDataLength);
  1.7854 +    }
  1.7855 +
  1.7856 +    return msg;
  1.7857 +  },
  1.7858 +
  1.7859 +  /**
  1.7860 +   * Read and decode a SMS-STATUS-REPORT PDU.
  1.7861 +   *
  1.7862 +   * @param msg
  1.7863 +   *        message object for output.
  1.7864 +   */
  1.7865 +  readStatusReportMessage: function(msg) {
  1.7866 +    // TP-Message-Reference
  1.7867 +    msg.messageRef = this.readHexOctet();
  1.7868 +    // TP-Recipient-Address
  1.7869 +    let recipientAddressLength = this.readHexOctet();
  1.7870 +    msg.recipient = this.readAddress(recipientAddressLength);
  1.7871 +    // TP-Service-Centre-Time-Stamp
  1.7872 +    msg.scts = this.readTimestamp();
  1.7873 +    // TP-Discharge-Time
  1.7874 +    msg.dt = this.readTimestamp();
  1.7875 +    // TP-Status
  1.7876 +    msg.status = this.readHexOctet();
  1.7877 +
  1.7878 +    this.readExtraParams(msg);
  1.7879 +
  1.7880 +    return msg;
  1.7881 +  },
  1.7882 +
  1.7883 +  /**
  1.7884 +   * Serialize a SMS-SUBMIT PDU message and write it to the output stream.
  1.7885 +   *
  1.7886 +   * This method expects that a data coding scheme has been chosen already
  1.7887 +   * and that the length of the user data payload in that encoding is known,
  1.7888 +   * too. Both go hand in hand together anyway.
  1.7889 +   *
  1.7890 +   * @param address
  1.7891 +   *        String containing the address (number) of the SMS receiver
  1.7892 +   * @param userData
  1.7893 +   *        String containing the message to be sent as user data
  1.7894 +   * @param dcs
  1.7895 +   *        Data coding scheme. One of the PDU_DCS_MSG_CODING_*BITS_ALPHABET
  1.7896 +   *        constants.
  1.7897 +   * @param userDataHeaderLength
  1.7898 +   *        Length of embedded user data header, in bytes. The whole header
  1.7899 +   *        size will be userDataHeaderLength + 1; 0 for no header.
  1.7900 +   * @param encodedBodyLength
  1.7901 +   *        Length of the user data when encoded with the given DCS. For UCS2,
  1.7902 +   *        in bytes; for 7-bit, in septets.
  1.7903 +   * @param langIndex
  1.7904 +   *        Table index used for normal 7-bit encoded character lookup.
  1.7905 +   * @param langShiftIndex
  1.7906 +   *        Table index used for escaped 7-bit encoded character lookup.
  1.7907 +   * @param requestStatusReport
  1.7908 +   *        Request status report.
  1.7909 +   */
  1.7910 +  writeMessage: function(options) {
  1.7911 +    if (DEBUG) {
  1.7912 +      this.context.debug("writeMessage: " + JSON.stringify(options));
  1.7913 +    }
  1.7914 +    let Buf = this.context.Buf;
  1.7915 +    let address = options.number;
  1.7916 +    let body = options.body;
  1.7917 +    let dcs = options.dcs;
  1.7918 +    let userDataHeaderLength = options.userDataHeaderLength;
  1.7919 +    let encodedBodyLength = options.encodedBodyLength;
  1.7920 +    let langIndex = options.langIndex;
  1.7921 +    let langShiftIndex = options.langShiftIndex;
  1.7922 +
  1.7923 +    // SMS-SUBMIT Format:
  1.7924 +    //
  1.7925 +    // PDU Type - 1 octet
  1.7926 +    // Message Reference - 1 octet
  1.7927 +    // DA - Destination Address - 2 to 12 octets
  1.7928 +    // PID - Protocol Identifier - 1 octet
  1.7929 +    // DCS - Data Coding Scheme - 1 octet
  1.7930 +    // VP - Validity Period - 0, 1 or 7 octets
  1.7931 +    // UDL - User Data Length - 1 octet
  1.7932 +    // UD - User Data - 140 octets
  1.7933 +
  1.7934 +    let addressFormat = PDU_TOA_ISDN; // 81
  1.7935 +    if (address[0] == '+') {
  1.7936 +      addressFormat = PDU_TOA_INTERNATIONAL | PDU_TOA_ISDN; // 91
  1.7937 +      address = address.substring(1);
  1.7938 +    }
  1.7939 +    //TODO validity is unsupported for now
  1.7940 +    let validity = 0;
  1.7941 +
  1.7942 +    let headerOctets = (userDataHeaderLength ? userDataHeaderLength + 1 : 0);
  1.7943 +    let paddingBits;
  1.7944 +    let userDataLengthInSeptets;
  1.7945 +    let userDataLengthInOctets;
  1.7946 +    if (dcs == PDU_DCS_MSG_CODING_7BITS_ALPHABET) {
  1.7947 +      let headerSeptets = Math.ceil(headerOctets * 8 / 7);
  1.7948 +      userDataLengthInSeptets = headerSeptets + encodedBodyLength;
  1.7949 +      userDataLengthInOctets = Math.ceil(userDataLengthInSeptets * 7 / 8);
  1.7950 +      paddingBits = headerSeptets * 7 - headerOctets * 8;
  1.7951 +    } else {
  1.7952 +      userDataLengthInOctets = headerOctets + encodedBodyLength;
  1.7953 +      paddingBits = 0;
  1.7954 +    }
  1.7955 +
  1.7956 +    let pduOctetLength = 4 + // PDU Type, Message Ref, address length + format
  1.7957 +                         Math.ceil(address.length / 2) +
  1.7958 +                         3 + // PID, DCS, UDL
  1.7959 +                         userDataLengthInOctets;
  1.7960 +    if (validity) {
  1.7961 +      //TODO: add more to pduOctetLength
  1.7962 +    }
  1.7963 +
  1.7964 +    // Start the string. Since octets are represented in hex, we will need
  1.7965 +    // twice as many characters as octets.
  1.7966 +    Buf.writeInt32(pduOctetLength * 2);
  1.7967 +
  1.7968 +    // - PDU-TYPE-
  1.7969 +
  1.7970 +    // +--------+----------+---------+---------+--------+---------+
  1.7971 +    // | RP (1) | UDHI (1) | SRR (1) | VPF (2) | RD (1) | MTI (2) |
  1.7972 +    // +--------+----------+---------+---------+--------+---------+
  1.7973 +    // RP:    0   Reply path parameter is not set
  1.7974 +    //        1   Reply path parameter is set
  1.7975 +    // UDHI:  0   The UD Field contains only the short message
  1.7976 +    //        1   The beginning of the UD field contains a header in addition
  1.7977 +    //            of the short message
  1.7978 +    // SRR:   0   A status report is not requested
  1.7979 +    //        1   A status report is requested
  1.7980 +    // VPF:   bit4  bit3
  1.7981 +    //        0     0     VP field is not present
  1.7982 +    //        0     1     Reserved
  1.7983 +    //        1     0     VP field present an integer represented (relative)
  1.7984 +    //        1     1     VP field present a semi-octet represented (absolute)
  1.7985 +    // RD:        Instruct the SMSC to accept(0) or reject(1) an SMS-SUBMIT
  1.7986 +    //            for a short message still held in the SMSC which has the same
  1.7987 +    //            MR and DA as a previously submitted short message from the
  1.7988 +    //            same OA
  1.7989 +    // MTI:   bit1  bit0    Message Type
  1.7990 +    //        0     0       SMS-DELIVER (SMSC ==> MS)
  1.7991 +    //        0     1       SMS-SUBMIT (MS ==> SMSC)
  1.7992 +
  1.7993 +    // PDU type. MTI is set to SMS-SUBMIT
  1.7994 +    let firstOctet = PDU_MTI_SMS_SUBMIT;
  1.7995 +
  1.7996 +    // Status-Report-Request
  1.7997 +    if (options.requestStatusReport) {
  1.7998 +      firstOctet |= PDU_SRI_SRR;
  1.7999 +    }
  1.8000 +
  1.8001 +    // Validity period
  1.8002 +    if (validity) {
  1.8003 +      //TODO: not supported yet, OR with one of PDU_VPF_*
  1.8004 +    }
  1.8005 +    // User data header indicator
  1.8006 +    if (headerOctets) {
  1.8007 +      firstOctet |= PDU_UDHI;
  1.8008 +    }
  1.8009 +    this.writeHexOctet(firstOctet);
  1.8010 +
  1.8011 +    // Message reference 00
  1.8012 +    this.writeHexOctet(0x00);
  1.8013 +
  1.8014 +    // - Destination Address -
  1.8015 +    this.writeHexOctet(address.length);
  1.8016 +    this.writeHexOctet(addressFormat);
  1.8017 +    this.writeSwappedNibbleBCD(address);
  1.8018 +
  1.8019 +    // - Protocol Identifier -
  1.8020 +    this.writeHexOctet(0x00);
  1.8021 +
  1.8022 +    // - Data coding scheme -
  1.8023 +    // For now it assumes bits 7..4 = 1111 except for the 16 bits use case
  1.8024 +    this.writeHexOctet(dcs);
  1.8025 +
  1.8026 +    // - Validity Period -
  1.8027 +    if (validity) {
  1.8028 +      this.writeHexOctet(validity);
  1.8029 +    }
  1.8030 +
  1.8031 +    // - User Data -
  1.8032 +    if (dcs == PDU_DCS_MSG_CODING_7BITS_ALPHABET) {
  1.8033 +      this.writeHexOctet(userDataLengthInSeptets);
  1.8034 +    } else {
  1.8035 +      this.writeHexOctet(userDataLengthInOctets);
  1.8036 +    }
  1.8037 +
  1.8038 +    if (headerOctets) {
  1.8039 +      this.writeUserDataHeader(options);
  1.8040 +    }
  1.8041 +
  1.8042 +    switch (dcs) {
  1.8043 +      case PDU_DCS_MSG_CODING_7BITS_ALPHABET:
  1.8044 +        this.writeStringAsSeptets(body, paddingBits, langIndex, langShiftIndex);
  1.8045 +        break;
  1.8046 +      case PDU_DCS_MSG_CODING_8BITS_ALPHABET:
  1.8047 +        // Unsupported.
  1.8048 +        break;
  1.8049 +      case PDU_DCS_MSG_CODING_16BITS_ALPHABET:
  1.8050 +        this.writeUCS2String(body);
  1.8051 +        break;
  1.8052 +    }
  1.8053 +
  1.8054 +    // End of the string. The string length is always even by definition, so
  1.8055 +    // we write two \0 delimiters.
  1.8056 +    Buf.writeUint16(0);
  1.8057 +    Buf.writeUint16(0);
  1.8058 +  },
  1.8059 +
  1.8060 +  /**
  1.8061 +   * Read GSM CBS message serial number.
  1.8062 +   *
  1.8063 +   * @param msg
  1.8064 +   *        message object for output.
  1.8065 +   *
  1.8066 +   * @see 3GPP TS 23.041 section 9.4.1.2.1
  1.8067 +   */
  1.8068 +  readCbSerialNumber: function(msg) {
  1.8069 +    let Buf = this.context.Buf;
  1.8070 +    msg.serial = Buf.readUint8() << 8 | Buf.readUint8();
  1.8071 +    msg.geographicalScope = (msg.serial >>> 14) & 0x03;
  1.8072 +    msg.messageCode = (msg.serial >>> 4) & 0x03FF;
  1.8073 +    msg.updateNumber = msg.serial & 0x0F;
  1.8074 +  },
  1.8075 +
  1.8076 +  /**
  1.8077 +   * Read GSM CBS message message identifier.
  1.8078 +   *
  1.8079 +   * @param msg
  1.8080 +   *        message object for output.
  1.8081 +   *
  1.8082 +   * @see 3GPP TS 23.041 section 9.4.1.2.2
  1.8083 +   */
  1.8084 +  readCbMessageIdentifier: function(msg) {
  1.8085 +    let Buf = this.context.Buf;
  1.8086 +    msg.messageId = Buf.readUint8() << 8 | Buf.readUint8();
  1.8087 +
  1.8088 +    if ((msg.format != CB_FORMAT_ETWS)
  1.8089 +        && (msg.messageId >= CB_GSM_MESSAGEID_ETWS_BEGIN)
  1.8090 +        && (msg.messageId <= CB_GSM_MESSAGEID_ETWS_END)) {
  1.8091 +      // `In the case of transmitting CBS message for ETWS, a part of
  1.8092 +      // Message Code can be used to command mobile terminals to activate
  1.8093 +      // emergency user alert and message popup in order to alert the users.`
  1.8094 +      msg.etws = {
  1.8095 +        emergencyUserAlert: msg.messageCode & 0x0200 ? true : false,
  1.8096 +        popup:              msg.messageCode & 0x0100 ? true : false
  1.8097 +      };
  1.8098 +
  1.8099 +      let warningType = msg.messageId - CB_GSM_MESSAGEID_ETWS_BEGIN;
  1.8100 +      if (warningType < CB_ETWS_WARNING_TYPE_NAMES.length) {
  1.8101 +        msg.etws.warningType = warningType;
  1.8102 +      }
  1.8103 +    }
  1.8104 +  },
  1.8105 +
  1.8106 +  /**
  1.8107 +   * Read CBS Data Coding Scheme.
  1.8108 +   *
  1.8109 +   * @param msg
  1.8110 +   *        message object for output.
  1.8111 +   *
  1.8112 +   * @see 3GPP TS 23.038 section 5.
  1.8113 +   */
  1.8114 +  readCbDataCodingScheme: function(msg) {
  1.8115 +    let dcs = this.context.Buf.readUint8();
  1.8116 +    if (DEBUG) this.context.debug("PDU: read CBS dcs: " + dcs);
  1.8117 +
  1.8118 +    let language = null, hasLanguageIndicator = false;
  1.8119 +    // `Any reserved codings shall be assumed to be the GSM 7bit default
  1.8120 +    // alphabet.`
  1.8121 +    let encoding = PDU_DCS_MSG_CODING_7BITS_ALPHABET;
  1.8122 +    let messageClass = PDU_DCS_MSG_CLASS_NORMAL;
  1.8123 +
  1.8124 +    switch (dcs & PDU_DCS_CODING_GROUP_BITS) {
  1.8125 +      case 0x00: // 0000
  1.8126 +        language = CB_DCS_LANG_GROUP_1[dcs & 0x0F];
  1.8127 +        break;
  1.8128 +
  1.8129 +      case 0x10: // 0001
  1.8130 +        switch (dcs & 0x0F) {
  1.8131 +          case 0x00:
  1.8132 +            hasLanguageIndicator = true;
  1.8133 +            break;
  1.8134 +          case 0x01:
  1.8135 +            encoding = PDU_DCS_MSG_CODING_16BITS_ALPHABET;
  1.8136 +            hasLanguageIndicator = true;
  1.8137 +            break;
  1.8138 +        }
  1.8139 +        break;
  1.8140 +
  1.8141 +      case 0x20: // 0010
  1.8142 +        language = CB_DCS_LANG_GROUP_2[dcs & 0x0F];
  1.8143 +        break;
  1.8144 +
  1.8145 +      case 0x40: // 01xx
  1.8146 +      case 0x50:
  1.8147 +      //case 0x60: Text Compression, not supported
  1.8148 +      //case 0x70: Text Compression, not supported
  1.8149 +      case 0x90: // 1001
  1.8150 +        encoding = (dcs & 0x0C);
  1.8151 +        if (encoding == 0x0C) {
  1.8152 +          encoding = PDU_DCS_MSG_CODING_7BITS_ALPHABET;
  1.8153 +        }
  1.8154 +        messageClass = (dcs & PDU_DCS_MSG_CLASS_BITS);
  1.8155 +        break;
  1.8156 +
  1.8157 +      case 0xF0:
  1.8158 +        encoding = (dcs & 0x04) ? PDU_DCS_MSG_CODING_8BITS_ALPHABET
  1.8159 +                                : PDU_DCS_MSG_CODING_7BITS_ALPHABET;
  1.8160 +        switch(dcs & PDU_DCS_MSG_CLASS_BITS) {
  1.8161 +          case 0x01: messageClass = PDU_DCS_MSG_CLASS_USER_1; break;
  1.8162 +          case 0x02: messageClass = PDU_DCS_MSG_CLASS_USER_2; break;
  1.8163 +          case 0x03: messageClass = PDU_DCS_MSG_CLASS_3; break;
  1.8164 +        }
  1.8165 +        break;
  1.8166 +
  1.8167 +      case 0x30: // 0011 (Reserved)
  1.8168 +      case 0x80: // 1000 (Reserved)
  1.8169 +      case 0xA0: // 1010..1100 (Reserved)
  1.8170 +      case 0xB0:
  1.8171 +      case 0xC0:
  1.8172 +        break;
  1.8173 +
  1.8174 +      default:
  1.8175 +        throw new Error("Unsupported CBS data coding scheme: " + dcs);
  1.8176 +    }
  1.8177 +
  1.8178 +    msg.dcs = dcs;
  1.8179 +    msg.encoding = encoding;
  1.8180 +    msg.language = language;
  1.8181 +    msg.messageClass = GECKO_SMS_MESSAGE_CLASSES[messageClass];
  1.8182 +    msg.hasLanguageIndicator = hasLanguageIndicator;
  1.8183 +  },
  1.8184 +
  1.8185 +  /**
  1.8186 +   * Read GSM CBS message page parameter.
  1.8187 +   *
  1.8188 +   * @param msg
  1.8189 +   *        message object for output.
  1.8190 +   *
  1.8191 +   * @see 3GPP TS 23.041 section 9.4.1.2.4
  1.8192 +   */
  1.8193 +  readCbPageParameter: function(msg) {
  1.8194 +    let octet = this.context.Buf.readUint8();
  1.8195 +    msg.pageIndex = (octet >>> 4) & 0x0F;
  1.8196 +    msg.numPages = octet & 0x0F;
  1.8197 +    if (!msg.pageIndex || !msg.numPages) {
  1.8198 +      // `If a mobile receives the code 0000 in either the first field or the
  1.8199 +      // second field then it shall treat the CBS message exactly the same as a
  1.8200 +      // CBS message with page parameter 0001 0001 (i.e. a single page message).`
  1.8201 +      msg.pageIndex = msg.numPages = 1;
  1.8202 +    }
  1.8203 +  },
  1.8204 +
  1.8205 +  /**
  1.8206 +   * Read ETWS Primary Notification message warning type.
  1.8207 +   *
  1.8208 +   * @param msg
  1.8209 +   *        message object for output.
  1.8210 +   *
  1.8211 +   * @see 3GPP TS 23.041 section 9.3.24
  1.8212 +   */
  1.8213 +  readCbWarningType: function(msg) {
  1.8214 +    let Buf = this.context.Buf;
  1.8215 +    let word = Buf.readUint8() << 8 | Buf.readUint8();
  1.8216 +    msg.etws = {
  1.8217 +      warningType:        (word >>> 9) & 0x7F,
  1.8218 +      popup:              word & 0x80 ? true : false,
  1.8219 +      emergencyUserAlert: word & 0x100 ? true : false
  1.8220 +    };
  1.8221 +  },
  1.8222 +
  1.8223 +  /**
  1.8224 +   * Read CBS-Message-Information-Page
  1.8225 +   *
  1.8226 +   * @param msg
  1.8227 +   *        message object for output.
  1.8228 +   * @param length
  1.8229 +   *        length of cell broadcast data to read in octets.
  1.8230 +   *
  1.8231 +   * @see 3GPP TS 23.041 section 9.3.19
  1.8232 +   */
  1.8233 +  readGsmCbData: function(msg, length) {
  1.8234 +    let Buf = this.context.Buf;
  1.8235 +    let bufAdapter = {
  1.8236 +      context: this.context,
  1.8237 +      readHexOctet: function() {
  1.8238 +        return Buf.readUint8();
  1.8239 +      }
  1.8240 +    };
  1.8241 +
  1.8242 +    msg.body = null;
  1.8243 +    msg.data = null;
  1.8244 +    switch (msg.encoding) {
  1.8245 +      case PDU_DCS_MSG_CODING_7BITS_ALPHABET:
  1.8246 +        msg.body = this.readSeptetsToString.call(bufAdapter,
  1.8247 +                                                 (length * 8 / 7), 0,
  1.8248 +                                                 PDU_NL_IDENTIFIER_DEFAULT,
  1.8249 +                                                 PDU_NL_IDENTIFIER_DEFAULT);
  1.8250 +        if (msg.hasLanguageIndicator) {
  1.8251 +          msg.language = msg.body.substring(0, 2);
  1.8252 +          msg.body = msg.body.substring(3);
  1.8253 +        }
  1.8254 +        break;
  1.8255 +
  1.8256 +      case PDU_DCS_MSG_CODING_8BITS_ALPHABET:
  1.8257 +        msg.data = Buf.readUint8Array(length);
  1.8258 +        break;
  1.8259 +
  1.8260 +      case PDU_DCS_MSG_CODING_16BITS_ALPHABET:
  1.8261 +        if (msg.hasLanguageIndicator) {
  1.8262 +          msg.language = this.readSeptetsToString.call(bufAdapter, 2, 0,
  1.8263 +                                                       PDU_NL_IDENTIFIER_DEFAULT,
  1.8264 +                                                       PDU_NL_IDENTIFIER_DEFAULT);
  1.8265 +          length -= 2;
  1.8266 +        }
  1.8267 +        msg.body = this.readUCS2String.call(bufAdapter, length);
  1.8268 +        break;
  1.8269 +    }
  1.8270 +  },
  1.8271 +
  1.8272 +  /**
  1.8273 +   * Read Cell GSM/ETWS/UMTS Broadcast Message.
  1.8274 +   *
  1.8275 +   * @param pduLength
  1.8276 +   *        total length of the incoming PDU in octets.
  1.8277 +   */
  1.8278 +  readCbMessage: function(pduLength) {
  1.8279 +    // Validity                                                   GSM ETWS UMTS
  1.8280 +    let msg = {
  1.8281 +      // Internally used in ril_worker:
  1.8282 +      serial:               null,                              //  O   O    O
  1.8283 +      updateNumber:         null,                              //  O   O    O
  1.8284 +      format:               null,                              //  O   O    O
  1.8285 +      dcs:                  0x0F,                              //  O   X    O
  1.8286 +      encoding:             PDU_DCS_MSG_CODING_7BITS_ALPHABET, //  O   X    O
  1.8287 +      hasLanguageIndicator: false,                             //  O   X    O
  1.8288 +      data:                 null,                              //  O   X    O
  1.8289 +      body:                 null,                              //  O   X    O
  1.8290 +      pageIndex:            1,                                 //  O   X    X
  1.8291 +      numPages:             1,                                 //  O   X    X
  1.8292 +
  1.8293 +      // DOM attributes:
  1.8294 +      geographicalScope:    null,                              //  O   O    O
  1.8295 +      messageCode:          null,                              //  O   O    O
  1.8296 +      messageId:            null,                              //  O   O    O
  1.8297 +      language:             null,                              //  O   X    O
  1.8298 +      fullBody:             null,                              //  O   X    O
  1.8299 +      fullData:             null,                              //  O   X    O
  1.8300 +      messageClass:         GECKO_SMS_MESSAGE_CLASSES[PDU_DCS_MSG_CLASS_NORMAL], //  O   x    O
  1.8301 +      etws:                 null                               //  ?   O    ?
  1.8302 +      /*{
  1.8303 +        warningType:        null,                              //  X   O    X
  1.8304 +        popup:              false,                             //  X   O    X
  1.8305 +        emergencyUserAlert: false,                             //  X   O    X
  1.8306 +      }*/
  1.8307 +    };
  1.8308 +
  1.8309 +    if (pduLength <= CB_MESSAGE_SIZE_ETWS) {
  1.8310 +      msg.format = CB_FORMAT_ETWS;
  1.8311 +      return this.readEtwsCbMessage(msg);
  1.8312 +    }
  1.8313 +
  1.8314 +    if (pduLength <= CB_MESSAGE_SIZE_GSM) {
  1.8315 +      msg.format = CB_FORMAT_GSM;
  1.8316 +      return this.readGsmCbMessage(msg, pduLength);
  1.8317 +    }
  1.8318 +
  1.8319 +    return null;
  1.8320 +  },
  1.8321 +
  1.8322 +  /**
  1.8323 +   * Read GSM Cell Broadcast Message.
  1.8324 +   *
  1.8325 +   * @param msg
  1.8326 +   *        message object for output.
  1.8327 +   * @param pduLength
  1.8328 +   *        total length of the incomint PDU in octets.
  1.8329 +   *
  1.8330 +   * @see 3GPP TS 23.041 clause 9.4.1.2
  1.8331 +   */
  1.8332 +  readGsmCbMessage: function(msg, pduLength) {
  1.8333 +    this.readCbSerialNumber(msg);
  1.8334 +    this.readCbMessageIdentifier(msg);
  1.8335 +    this.readCbDataCodingScheme(msg);
  1.8336 +    this.readCbPageParameter(msg);
  1.8337 +
  1.8338 +    // GSM CB message header takes 6 octets.
  1.8339 +    this.readGsmCbData(msg, pduLength - 6);
  1.8340 +
  1.8341 +    return msg;
  1.8342 +  },
  1.8343 +
  1.8344 +  /**
  1.8345 +   * Read ETWS Primary Notification Message.
  1.8346 +   *
  1.8347 +   * @param msg
  1.8348 +   *        message object for output.
  1.8349 +   *
  1.8350 +   * @see 3GPP TS 23.041 clause 9.4.1.3
  1.8351 +   */
  1.8352 +  readEtwsCbMessage: function(msg) {
  1.8353 +    this.readCbSerialNumber(msg);
  1.8354 +    this.readCbMessageIdentifier(msg);
  1.8355 +    this.readCbWarningType(msg);
  1.8356 +
  1.8357 +    // Octet 7..56 is Warning Security Information. However, according to
  1.8358 +    // section 9.4.1.3.6, `The UE shall ignore this parameter.` So we just skip
  1.8359 +    // processing it here.
  1.8360 +
  1.8361 +    return msg;
  1.8362 +  },
  1.8363 +
  1.8364 +  /**
  1.8365 +   * Read network name.
  1.8366 +   *
  1.8367 +   * @param len Length of the information element.
  1.8368 +   * @return
  1.8369 +   *   {
  1.8370 +   *     networkName: network name.
  1.8371 +   *     shouldIncludeCi: Should Country's initials included in text string.
  1.8372 +   *   }
  1.8373 +   * @see TS 24.008 clause 10.5.3.5a.
  1.8374 +   */
  1.8375 +  readNetworkName: function(len) {
  1.8376 +    // According to TS 24.008 Sec. 10.5.3.5a, the first octet is:
  1.8377 +    // bit 8: must be 1.
  1.8378 +    // bit 5-7: Text encoding.
  1.8379 +    //          000 - GSM default alphabet.
  1.8380 +    //          001 - UCS2 (16 bit).
  1.8381 +    //          else - reserved.
  1.8382 +    // bit 4: MS should add the letters for Country's Initials and a space
  1.8383 +    //        to the text string if this bit is true.
  1.8384 +    // bit 1-3: number of spare bits in last octet.
  1.8385 +
  1.8386 +    let codingInfo = this.readHexOctet();
  1.8387 +    if (!(codingInfo & 0x80)) {
  1.8388 +      return null;
  1.8389 +    }
  1.8390 +
  1.8391 +    let textEncoding = (codingInfo & 0x70) >> 4;
  1.8392 +    let shouldIncludeCountryInitials = !!(codingInfo & 0x08);
  1.8393 +    let spareBits = codingInfo & 0x07;
  1.8394 +    let resultString;
  1.8395 +
  1.8396 +    switch (textEncoding) {
  1.8397 +    case 0:
  1.8398 +      // GSM Default alphabet.
  1.8399 +      resultString = this.readSeptetsToString(
  1.8400 +        ((len - 1) * 8 - spareBits) / 7, 0,
  1.8401 +        PDU_NL_IDENTIFIER_DEFAULT,
  1.8402 +        PDU_NL_IDENTIFIER_DEFAULT);
  1.8403 +      break;
  1.8404 +    case 1:
  1.8405 +      // UCS2 encoded.
  1.8406 +      resultString = this.readUCS2String(len - 1);
  1.8407 +      break;
  1.8408 +    default:
  1.8409 +      // Not an available text coding.
  1.8410 +      return null;
  1.8411 +    }
  1.8412 +
  1.8413 +    // TODO - Bug 820286: According to shouldIncludeCountryInitials, add
  1.8414 +    // country initials to the resulting string.
  1.8415 +    return resultString;
  1.8416 +  }
  1.8417 +};
  1.8418 +
  1.8419 +/**
  1.8420 + * Provide buffer with bitwise read/write function so make encoding/decoding easier.
  1.8421 + */
  1.8422 +function BitBufferHelperObject(/* unused */aContext) {
  1.8423 +  this.readBuffer = [];
  1.8424 +  this.writeBuffer = [];
  1.8425 +}
  1.8426 +BitBufferHelperObject.prototype = {
  1.8427 +  readCache: 0,
  1.8428 +  readCacheSize: 0,
  1.8429 +  readBuffer: null,
  1.8430 +  readIndex: 0,
  1.8431 +  writeCache: 0,
  1.8432 +  writeCacheSize: 0,
  1.8433 +  writeBuffer: null,
  1.8434 +
  1.8435 +  // Max length is 32 because we use integer as read/write cache.
  1.8436 +  // All read/write functions are implemented based on bitwise operation.
  1.8437 +  readBits: function(length) {
  1.8438 +    if (length <= 0 || length > 32) {
  1.8439 +      return null;
  1.8440 +    }
  1.8441 +
  1.8442 +    if (length > this.readCacheSize) {
  1.8443 +      let bytesToRead = Math.ceil((length - this.readCacheSize) / 8);
  1.8444 +      for(let i = 0; i < bytesToRead; i++) {
  1.8445 +        this.readCache = (this.readCache << 8) | (this.readBuffer[this.readIndex++] & 0xFF);
  1.8446 +        this.readCacheSize += 8;
  1.8447 +      }
  1.8448 +    }
  1.8449 +
  1.8450 +    let bitOffset = (this.readCacheSize - length),
  1.8451 +        resultMask = (1 << length) - 1,
  1.8452 +        result = 0;
  1.8453 +
  1.8454 +    result = (this.readCache >> bitOffset) & resultMask;
  1.8455 +    this.readCacheSize -= length;
  1.8456 +
  1.8457 +    return result;
  1.8458 +  },
  1.8459 +
  1.8460 +  backwardReadPilot: function(length) {
  1.8461 +    if (length <= 0) {
  1.8462 +      return;
  1.8463 +    }
  1.8464 +
  1.8465 +    // Zero-based position.
  1.8466 +    let bitIndexToRead = this.readIndex * 8 - this.readCacheSize - length;
  1.8467 +
  1.8468 +    if (bitIndexToRead < 0) {
  1.8469 +      return;
  1.8470 +    }
  1.8471 +
  1.8472 +    // Update readIndex, readCache, readCacheSize accordingly.
  1.8473 +    let readBits = bitIndexToRead % 8;
  1.8474 +    this.readIndex = Math.floor(bitIndexToRead / 8) + ((readBits) ? 1 : 0);
  1.8475 +    this.readCache = (readBits) ? this.readBuffer[this.readIndex - 1] : 0;
  1.8476 +    this.readCacheSize = (readBits) ? (8 - readBits) : 0;
  1.8477 +  },
  1.8478 +
  1.8479 +  writeBits: function(value, length) {
  1.8480 +    if (length <= 0 || length > 32) {
  1.8481 +      return;
  1.8482 +    }
  1.8483 +
  1.8484 +    let totalLength = length + this.writeCacheSize;
  1.8485 +
  1.8486 +    // 8-byte cache not full
  1.8487 +    if (totalLength < 8) {
  1.8488 +      let valueMask = (1 << length) - 1;
  1.8489 +      this.writeCache = (this.writeCache << length) | (value & valueMask);
  1.8490 +      this.writeCacheSize += length;
  1.8491 +      return;
  1.8492 +    }
  1.8493 +
  1.8494 +    // Deal with unaligned part
  1.8495 +    if (this.writeCacheSize) {
  1.8496 +      let mergeLength = 8 - this.writeCacheSize,
  1.8497 +          valueMask = (1 << mergeLength) - 1;
  1.8498 +
  1.8499 +      this.writeCache = (this.writeCache << mergeLength) | ((value >> (length - mergeLength)) & valueMask);
  1.8500 +      this.writeBuffer.push(this.writeCache & 0xFF);
  1.8501 +      length -= mergeLength;
  1.8502 +    }
  1.8503 +
  1.8504 +    // Aligned part, just copy
  1.8505 +    while (length >= 8) {
  1.8506 +      length -= 8;
  1.8507 +      this.writeBuffer.push((value >> length) & 0xFF);
  1.8508 +    }
  1.8509 +
  1.8510 +    // Rest part is saved into cache
  1.8511 +    this.writeCacheSize = length;
  1.8512 +    this.writeCache = value & ((1 << length) - 1);
  1.8513 +
  1.8514 +    return;
  1.8515 +  },
  1.8516 +
  1.8517 +  // Drop what still in read cache and goto next 8-byte alignment.
  1.8518 +  // There might be a better naming.
  1.8519 +  nextOctetAlign: function() {
  1.8520 +    this.readCache = 0;
  1.8521 +    this.readCacheSize = 0;
  1.8522 +  },
  1.8523 +
  1.8524 +  // Flush current write cache to Buf with padding 0s.
  1.8525 +  // There might be a better naming.
  1.8526 +  flushWithPadding: function() {
  1.8527 +    if (this.writeCacheSize) {
  1.8528 +      this.writeBuffer.push(this.writeCache << (8 - this.writeCacheSize));
  1.8529 +    }
  1.8530 +    this.writeCache = 0;
  1.8531 +    this.writeCacheSize = 0;
  1.8532 +  },
  1.8533 +
  1.8534 +  startWrite: function(dataBuffer) {
  1.8535 +    this.writeBuffer = dataBuffer;
  1.8536 +    this.writeCache = 0;
  1.8537 +    this.writeCacheSize = 0;
  1.8538 +  },
  1.8539 +
  1.8540 +  startRead: function(dataBuffer) {
  1.8541 +    this.readBuffer = dataBuffer;
  1.8542 +    this.readCache = 0;
  1.8543 +    this.readCacheSize = 0;
  1.8544 +    this.readIndex = 0;
  1.8545 +  },
  1.8546 +
  1.8547 +  getWriteBufferSize: function() {
  1.8548 +    return this.writeBuffer.length;
  1.8549 +  },
  1.8550 +
  1.8551 +  overwriteWriteBuffer: function(position, data) {
  1.8552 +    let writeLength = data.length;
  1.8553 +    if (writeLength + position >= this.writeBuffer.length) {
  1.8554 +      writeLength = this.writeBuffer.length - position;
  1.8555 +    }
  1.8556 +    for (let i = 0; i < writeLength; i++) {
  1.8557 +      this.writeBuffer[i + position] = data[i];
  1.8558 +    }
  1.8559 +  }
  1.8560 +};
  1.8561 +
  1.8562 +/**
  1.8563 + * Helper for CDMA PDU
  1.8564 + *
  1.8565 + * Currently, some function are shared with GsmPDUHelper, they should be
  1.8566 + * moved from GsmPDUHelper to a common object shared among GsmPDUHelper and
  1.8567 + * CdmaPDUHelper.
  1.8568 + */
  1.8569 +function CdmaPDUHelperObject(aContext) {
  1.8570 +  this.context = aContext;
  1.8571 +}
  1.8572 +CdmaPDUHelperObject.prototype = {
  1.8573 +  context: null,
  1.8574 +
  1.8575 +  //       1..........C
  1.8576 +  // Only "1234567890*#" is defined in C.S0005-D v2.0
  1.8577 +  dtmfChars: ".1234567890*#...",
  1.8578 +
  1.8579 +  /**
  1.8580 +   * Entry point for SMS encoding, the options object is made compatible
  1.8581 +   * with existing writeMessage() of GsmPDUHelper, but less key is used.
  1.8582 +   *
  1.8583 +   * Current used key in options:
  1.8584 +   * @param number
  1.8585 +   *        String containing the address (number) of the SMS receiver
  1.8586 +   * @param body
  1.8587 +   *        String containing the message to be sent, segmented part
  1.8588 +   * @param dcs
  1.8589 +   *        Data coding scheme. One of the PDU_DCS_MSG_CODING_*BITS_ALPHABET
  1.8590 +   *        constants.
  1.8591 +   * @param encodedBodyLength
  1.8592 +   *        Length of the user data when encoded with the given DCS. For UCS2,
  1.8593 +   *        in bytes; for 7-bit, in septets.
  1.8594 +   * @param requestStatusReport
  1.8595 +   *        Request status report.
  1.8596 +   * @param segmentRef
  1.8597 +   *        Reference number of concatenated SMS message
  1.8598 +   * @param segmentMaxSeq
  1.8599 +   *        Total number of concatenated SMS message
  1.8600 +   * @param segmentSeq
  1.8601 +   *        Sequence number of concatenated SMS message
  1.8602 +   */
  1.8603 +  writeMessage: function(options) {
  1.8604 +    if (DEBUG) {
  1.8605 +      this.context.debug("cdma_writeMessage: " + JSON.stringify(options));
  1.8606 +    }
  1.8607 +
  1.8608 +    // Get encoding
  1.8609 +    options.encoding = this.gsmDcsToCdmaEncoding(options.dcs);
  1.8610 +
  1.8611 +    // Common Header
  1.8612 +    if (options.segmentMaxSeq > 1) {
  1.8613 +      this.writeInt(PDU_CDMA_MSG_TELESERIVCIE_ID_WEMT);
  1.8614 +    } else {
  1.8615 +      this.writeInt(PDU_CDMA_MSG_TELESERIVCIE_ID_SMS);
  1.8616 +    }
  1.8617 +
  1.8618 +    this.writeInt(0);
  1.8619 +    this.writeInt(PDU_CDMA_MSG_CATEGORY_UNSPEC);
  1.8620 +
  1.8621 +    // Just fill out address info in byte, rild will encap them for us
  1.8622 +    let addrInfo = this.encodeAddr(options.number);
  1.8623 +    this.writeByte(addrInfo.digitMode);
  1.8624 +    this.writeByte(addrInfo.numberMode);
  1.8625 +    this.writeByte(addrInfo.numberType);
  1.8626 +    this.writeByte(addrInfo.numberPlan);
  1.8627 +    this.writeByte(addrInfo.address.length);
  1.8628 +    for (let i = 0; i < addrInfo.address.length; i++) {
  1.8629 +      this.writeByte(addrInfo.address[i]);
  1.8630 +    }
  1.8631 +
  1.8632 +    // Subaddress, not supported
  1.8633 +    this.writeByte(0);  // Subaddress : Type
  1.8634 +    this.writeByte(0);  // Subaddress : Odd
  1.8635 +    this.writeByte(0);  // Subaddress : length
  1.8636 +
  1.8637 +    // User Data
  1.8638 +    let encodeResult = this.encodeUserData(options);
  1.8639 +    this.writeByte(encodeResult.length);
  1.8640 +    for (let i = 0; i < encodeResult.length; i++) {
  1.8641 +      this.writeByte(encodeResult[i]);
  1.8642 +    }
  1.8643 +
  1.8644 +    encodeResult = null;
  1.8645 +  },
  1.8646 +
  1.8647 +  /**
  1.8648 +   * Data writters
  1.8649 +   */
  1.8650 +  writeInt: function(value) {
  1.8651 +    this.context.Buf.writeInt32(value);
  1.8652 +  },
  1.8653 +
  1.8654 +  writeByte: function(value) {
  1.8655 +    this.context.Buf.writeInt32(value & 0xFF);
  1.8656 +  },
  1.8657 +
  1.8658 +  /**
  1.8659 +   * Transform GSM DCS to CDMA encoding.
  1.8660 +   */
  1.8661 +  gsmDcsToCdmaEncoding: function(encoding) {
  1.8662 +    switch (encoding) {
  1.8663 +      case PDU_DCS_MSG_CODING_7BITS_ALPHABET:
  1.8664 +        return PDU_CDMA_MSG_CODING_7BITS_ASCII;
  1.8665 +      case PDU_DCS_MSG_CODING_8BITS_ALPHABET:
  1.8666 +        return PDU_CDMA_MSG_CODING_OCTET;
  1.8667 +      case PDU_DCS_MSG_CODING_16BITS_ALPHABET:
  1.8668 +        return PDU_CDMA_MSG_CODING_UNICODE;
  1.8669 +    }
  1.8670 +    throw new Error("gsmDcsToCdmaEncoding(): Invalid GSM SMS DCS value: " + encoding);
  1.8671 +  },
  1.8672 +
  1.8673 +  /**
  1.8674 +   * Encode address into CDMA address format, as a byte array.
  1.8675 +   *
  1.8676 +   * @see 3GGP2 C.S0015-B 2.0, 3.4.3.3 Address Parameters
  1.8677 +   *
  1.8678 +   * @param address
  1.8679 +   *        String of address to be encoded
  1.8680 +   */
  1.8681 +  encodeAddr: function(address) {
  1.8682 +    let result = {};
  1.8683 +
  1.8684 +    result.numberType = PDU_CDMA_MSG_ADDR_NUMBER_TYPE_UNKNOWN;
  1.8685 +    result.numberPlan = PDU_CDMA_MSG_ADDR_NUMBER_TYPE_UNKNOWN;
  1.8686 +
  1.8687 +    if (address[0] === '+') {
  1.8688 +      address = address.substring(1);
  1.8689 +    }
  1.8690 +
  1.8691 +    // Try encode with DTMF first
  1.8692 +    result.digitMode = PDU_CDMA_MSG_ADDR_DIGIT_MODE_DTMF;
  1.8693 +    result.numberMode = PDU_CDMA_MSG_ADDR_NUMBER_MODE_ANSI;
  1.8694 +
  1.8695 +    result.address = [];
  1.8696 +    for (let i = 0; i < address.length; i++) {
  1.8697 +      let addrDigit = this.dtmfChars.indexOf(address.charAt(i));
  1.8698 +      if (addrDigit < 0) {
  1.8699 +        result.digitMode = PDU_CDMA_MSG_ADDR_DIGIT_MODE_ASCII;
  1.8700 +        result.numberMode = PDU_CDMA_MSG_ADDR_NUMBER_MODE_ASCII;
  1.8701 +        result.address = [];
  1.8702 +        break;
  1.8703 +      }
  1.8704 +      result.address.push(addrDigit);
  1.8705 +    }
  1.8706 +
  1.8707 +    // Address can't be encoded with DTMF, then use 7-bit ASCII
  1.8708 +    if (result.digitMode !== PDU_CDMA_MSG_ADDR_DIGIT_MODE_DTMF) {
  1.8709 +      if (address.indexOf("@") !== -1) {
  1.8710 +        result.numberType = PDU_CDMA_MSG_ADDR_NUMBER_TYPE_NATIONAL;
  1.8711 +      }
  1.8712 +
  1.8713 +      for (let i = 0; i < address.length; i++) {
  1.8714 +        result.address.push(address.charCodeAt(i) & 0x7F);
  1.8715 +      }
  1.8716 +    }
  1.8717 +
  1.8718 +    return result;
  1.8719 +  },
  1.8720 +
  1.8721 +  /**
  1.8722 +   * Encode SMS contents in options into CDMA userData field.
  1.8723 +   * Corresponding and required subparameters will be added automatically.
  1.8724 +   *
  1.8725 +   * @see 3GGP2 C.S0015-B 2.0, 3.4.3.7 Bearer Data
  1.8726 +   *                           4.5 Bearer Data Parameters
  1.8727 +   *
  1.8728 +   * Current used key in options:
  1.8729 +   * @param body
  1.8730 +   *        String containing the message to be sent, segmented part
  1.8731 +   * @param encoding
  1.8732 +   *        Encoding method of CDMA, can be transformed from GSM DCS by function
  1.8733 +   *        cdmaPduHelp.gsmDcsToCdmaEncoding()
  1.8734 +   * @param encodedBodyLength
  1.8735 +   *        Length of the user data when encoded with the given DCS. For UCS2,
  1.8736 +   *        in bytes; for 7-bit, in septets.
  1.8737 +   * @param requestStatusReport
  1.8738 +   *        Request status report.
  1.8739 +   * @param segmentRef
  1.8740 +   *        Reference number of concatenated SMS message
  1.8741 +   * @param segmentMaxSeq
  1.8742 +   *        Total number of concatenated SMS message
  1.8743 +   * @param segmentSeq
  1.8744 +   *        Sequence number of concatenated SMS message
  1.8745 +   */
  1.8746 +  encodeUserData: function(options) {
  1.8747 +    let userDataBuffer = [];
  1.8748 +    this.context.BitBufferHelper.startWrite(userDataBuffer);
  1.8749 +
  1.8750 +    // Message Identifier
  1.8751 +    this.encodeUserDataMsgId(options);
  1.8752 +
  1.8753 +    // User Data
  1.8754 +    this.encodeUserDataMsg(options);
  1.8755 +
  1.8756 +    // Reply Option
  1.8757 +    this.encodeUserDataReplyOption(options);
  1.8758 +
  1.8759 +    return userDataBuffer;
  1.8760 +  },
  1.8761 +
  1.8762 +  /**
  1.8763 +   * User data subparameter encoder : Message Identifier
  1.8764 +   *
  1.8765 +   * @see 3GGP2 C.S0015-B 2.0, 4.5.1 Message Identifier
  1.8766 +   */
  1.8767 +  encodeUserDataMsgId: function(options) {
  1.8768 +    let BitBufferHelper = this.context.BitBufferHelper;
  1.8769 +    BitBufferHelper.writeBits(PDU_CDMA_MSG_USERDATA_MSG_ID, 8);
  1.8770 +    BitBufferHelper.writeBits(3, 8);
  1.8771 +    BitBufferHelper.writeBits(PDU_CDMA_MSG_TYPE_SUBMIT, 4);
  1.8772 +    BitBufferHelper.writeBits(1, 16); // TODO: How to get message ID?
  1.8773 +    if (options.segmentMaxSeq > 1) {
  1.8774 +      BitBufferHelper.writeBits(1, 1);
  1.8775 +    } else {
  1.8776 +      BitBufferHelper.writeBits(0, 1);
  1.8777 +    }
  1.8778 +
  1.8779 +    BitBufferHelper.flushWithPadding();
  1.8780 +  },
  1.8781 +
  1.8782 +  /**
  1.8783 +   * User data subparameter encoder : User Data
  1.8784 +   *
  1.8785 +   * @see 3GGP2 C.S0015-B 2.0, 4.5.2 User Data
  1.8786 +   */
  1.8787 +  encodeUserDataMsg: function(options) {
  1.8788 +    let BitBufferHelper = this.context.BitBufferHelper;
  1.8789 +    BitBufferHelper.writeBits(PDU_CDMA_MSG_USERDATA_BODY, 8);
  1.8790 +    // Reserve space for length
  1.8791 +    BitBufferHelper.writeBits(0, 8);
  1.8792 +    let lengthPosition = BitBufferHelper.getWriteBufferSize();
  1.8793 +
  1.8794 +    BitBufferHelper.writeBits(options.encoding, 5);
  1.8795 +
  1.8796 +    // Add user data header for message segement
  1.8797 +    let msgBody = options.body,
  1.8798 +        msgBodySize = (options.encoding === PDU_CDMA_MSG_CODING_7BITS_ASCII ?
  1.8799 +                       options.encodedBodyLength :
  1.8800 +                       msgBody.length);
  1.8801 +    if (options.segmentMaxSeq > 1) {
  1.8802 +      if (options.encoding === PDU_CDMA_MSG_CODING_7BITS_ASCII) {
  1.8803 +          BitBufferHelper.writeBits(msgBodySize + 7, 8); // Required length for user data header, in septet(7-bit)
  1.8804 +
  1.8805 +          BitBufferHelper.writeBits(5, 8);  // total header length 5 bytes
  1.8806 +          BitBufferHelper.writeBits(PDU_IEI_CONCATENATED_SHORT_MESSAGES_8BIT, 8);  // header id 0
  1.8807 +          BitBufferHelper.writeBits(3, 8);  // length of element for id 0 is 3
  1.8808 +          BitBufferHelper.writeBits(options.segmentRef & 0xFF, 8);      // Segement reference
  1.8809 +          BitBufferHelper.writeBits(options.segmentMaxSeq & 0xFF, 8);   // Max segment
  1.8810 +          BitBufferHelper.writeBits(options.segmentSeq & 0xFF, 8);      // Current segment
  1.8811 +          BitBufferHelper.writeBits(0, 1);  // Padding to make header data septet(7-bit) aligned
  1.8812 +        } else {
  1.8813 +          if (options.encoding === PDU_CDMA_MSG_CODING_UNICODE) {
  1.8814 +            BitBufferHelper.writeBits(msgBodySize + 3, 8); // Required length for user data header, in 16-bit
  1.8815 +          } else {
  1.8816 +            BitBufferHelper.writeBits(msgBodySize + 6, 8); // Required length for user data header, in octet(8-bit)
  1.8817 +          }
  1.8818 +
  1.8819 +          BitBufferHelper.writeBits(5, 8);  // total header length 5 bytes
  1.8820 +          BitBufferHelper.writeBits(PDU_IEI_CONCATENATED_SHORT_MESSAGES_8BIT, 8);  // header id 0
  1.8821 +          BitBufferHelper.writeBits(3, 8);  // length of element for id 0 is 3
  1.8822 +          BitBufferHelper.writeBits(options.segmentRef & 0xFF, 8);      // Segement reference
  1.8823 +          BitBufferHelper.writeBits(options.segmentMaxSeq & 0xFF, 8);   // Max segment
  1.8824 +          BitBufferHelper.writeBits(options.segmentSeq & 0xFF, 8);      // Current segment
  1.8825 +        }
  1.8826 +    } else {
  1.8827 +      BitBufferHelper.writeBits(msgBodySize, 8);
  1.8828 +    }
  1.8829 +
  1.8830 +    // Encode message based on encoding method
  1.8831 +    const langTable = PDU_NL_LOCKING_SHIFT_TABLES[PDU_NL_IDENTIFIER_DEFAULT];
  1.8832 +    const langShiftTable = PDU_NL_SINGLE_SHIFT_TABLES[PDU_NL_IDENTIFIER_DEFAULT];
  1.8833 +    for (let i = 0; i < msgBody.length; i++) {
  1.8834 +      switch (options.encoding) {
  1.8835 +        case PDU_CDMA_MSG_CODING_OCTET: {
  1.8836 +          let msgDigit = msgBody.charCodeAt(i);
  1.8837 +          BitBufferHelper.writeBits(msgDigit, 8);
  1.8838 +          break;
  1.8839 +        }
  1.8840 +        case PDU_CDMA_MSG_CODING_7BITS_ASCII: {
  1.8841 +          let msgDigit = msgBody.charCodeAt(i),
  1.8842 +              msgDigitChar = msgBody.charAt(i);
  1.8843 +
  1.8844 +          if (msgDigit >= 32) {
  1.8845 +            BitBufferHelper.writeBits(msgDigit, 7);
  1.8846 +          } else {
  1.8847 +            msgDigit = langTable.indexOf(msgDigitChar);
  1.8848 +
  1.8849 +            if (msgDigit === PDU_NL_EXTENDED_ESCAPE) {
  1.8850 +              break;
  1.8851 +            }
  1.8852 +            if (msgDigit >= 0) {
  1.8853 +              BitBufferHelper.writeBits(msgDigit, 7);
  1.8854 +            } else {
  1.8855 +              msgDigit = langShiftTable.indexOf(msgDigitChar);
  1.8856 +              if (msgDigit == -1) {
  1.8857 +                throw new Error("'" + msgDigitChar + "' is not in 7 bit alphabet "
  1.8858 +                                + langIndex + ":" + langShiftIndex + "!");
  1.8859 +              }
  1.8860 +
  1.8861 +              if (msgDigit === PDU_NL_RESERVED_CONTROL) {
  1.8862 +                break;
  1.8863 +              }
  1.8864 +
  1.8865 +              BitBufferHelper.writeBits(PDU_NL_EXTENDED_ESCAPE, 7);
  1.8866 +              BitBufferHelper.writeBits(msgDigit, 7);
  1.8867 +            }
  1.8868 +          }
  1.8869 +          break;
  1.8870 +        }
  1.8871 +        case PDU_CDMA_MSG_CODING_UNICODE: {
  1.8872 +          let msgDigit = msgBody.charCodeAt(i);
  1.8873 +          BitBufferHelper.writeBits(msgDigit, 16);
  1.8874 +          break;
  1.8875 +        }
  1.8876 +      }
  1.8877 +    }
  1.8878 +    BitBufferHelper.flushWithPadding();
  1.8879 +
  1.8880 +    // Fill length
  1.8881 +    let currentPosition = BitBufferHelper.getWriteBufferSize();
  1.8882 +    BitBufferHelper.overwriteWriteBuffer(lengthPosition - 1, [currentPosition - lengthPosition]);
  1.8883 +  },
  1.8884 +
  1.8885 +  /**
  1.8886 +   * User data subparameter encoder : Reply Option
  1.8887 +   *
  1.8888 +   * @see 3GGP2 C.S0015-B 2.0, 4.5.11 Reply Option
  1.8889 +   */
  1.8890 +  encodeUserDataReplyOption: function(options) {
  1.8891 +    if (options.requestStatusReport) {
  1.8892 +      let BitBufferHelper = this.context.BitBufferHelper;
  1.8893 +      BitBufferHelper.writeBits(PDU_CDMA_MSG_USERDATA_REPLY_OPTION, 8);
  1.8894 +      BitBufferHelper.writeBits(1, 8);
  1.8895 +      BitBufferHelper.writeBits(0, 1); // USER_ACK_REQ
  1.8896 +      BitBufferHelper.writeBits(1, 1); // DAK_REQ
  1.8897 +      BitBufferHelper.flushWithPadding();
  1.8898 +    }
  1.8899 +  },
  1.8900 +
  1.8901 +  /**
  1.8902 +   * Entry point for SMS decoding, the returned object is made compatible
  1.8903 +   * with existing readMessage() of GsmPDUHelper
  1.8904 +   */
  1.8905 +  readMessage: function() {
  1.8906 +    let message = {};
  1.8907 +
  1.8908 +    // Teleservice Identifier
  1.8909 +    message.teleservice = this.readInt();
  1.8910 +
  1.8911 +    // Message Type
  1.8912 +    let isServicePresent = this.readByte();
  1.8913 +    if (isServicePresent) {
  1.8914 +      message.messageType = PDU_CDMA_MSG_TYPE_BROADCAST;
  1.8915 +    } else {
  1.8916 +      if (message.teleservice) {
  1.8917 +        message.messageType = PDU_CDMA_MSG_TYPE_P2P;
  1.8918 +      } else {
  1.8919 +        message.messageType = PDU_CDMA_MSG_TYPE_ACK;
  1.8920 +      }
  1.8921 +    }
  1.8922 +
  1.8923 +    // Service Category
  1.8924 +    message.service = this.readInt();
  1.8925 +
  1.8926 +    // Originated Address
  1.8927 +    let addrInfo = {};
  1.8928 +    addrInfo.digitMode = (this.readInt() & 0x01);
  1.8929 +    addrInfo.numberMode = (this.readInt() & 0x01);
  1.8930 +    addrInfo.numberType = (this.readInt() & 0x01);
  1.8931 +    addrInfo.numberPlan = (this.readInt() & 0x01);
  1.8932 +    addrInfo.addrLength = this.readByte();
  1.8933 +    addrInfo.address = [];
  1.8934 +    for (let i = 0; i < addrInfo.addrLength; i++) {
  1.8935 +      addrInfo.address.push(this.readByte());
  1.8936 +    }
  1.8937 +    message.sender = this.decodeAddr(addrInfo);
  1.8938 +
  1.8939 +    // Originated Subaddress
  1.8940 +    addrInfo.Type = (this.readInt() & 0x07);
  1.8941 +    addrInfo.Odd = (this.readByte() & 0x01);
  1.8942 +    addrInfo.addrLength = this.readByte();
  1.8943 +    for (let i = 0; i < addrInfo.addrLength; i++) {
  1.8944 +      let addrDigit = this.readByte();
  1.8945 +      message.sender += String.fromCharCode(addrDigit);
  1.8946 +    }
  1.8947 +
  1.8948 +    // Bearer Data
  1.8949 +    this.decodeUserData(message);
  1.8950 +
  1.8951 +    // Bearer Data Sub-Parameter: User Data
  1.8952 +    let userData = message[PDU_CDMA_MSG_USERDATA_BODY];
  1.8953 +    [message.header, message.body, message.encoding, message.data] =
  1.8954 +      (userData) ? [userData.header, userData.body, userData.encoding, userData.data]
  1.8955 +                 : [null, null, null, null];
  1.8956 +
  1.8957 +    // Bearer Data Sub-Parameter: Message Status
  1.8958 +    // Success Delivery (0) if both Message Status and User Data are absent.
  1.8959 +    // Message Status absent (-1) if only User Data is available.
  1.8960 +    let msgStatus = message[PDU_CDMA_MSG_USER_DATA_MSG_STATUS];
  1.8961 +    [message.errorClass, message.msgStatus] =
  1.8962 +      (msgStatus) ? [msgStatus.errorClass, msgStatus.msgStatus]
  1.8963 +                  : ((message.body) ? [-1, -1] : [0, 0]);
  1.8964 +
  1.8965 +    // Transform message to GSM msg
  1.8966 +    let msg = {
  1.8967 +      SMSC:             "",
  1.8968 +      mti:              0,
  1.8969 +      udhi:             0,
  1.8970 +      sender:           message.sender,
  1.8971 +      recipient:        null,
  1.8972 +      pid:              PDU_PID_DEFAULT,
  1.8973 +      epid:             PDU_PID_DEFAULT,
  1.8974 +      dcs:              0,
  1.8975 +      mwi:              null,
  1.8976 +      replace:          false,
  1.8977 +      header:           message.header,
  1.8978 +      body:             message.body,
  1.8979 +      data:             message.data,
  1.8980 +      sentTimestamp:    message[PDU_CDMA_MSG_USERDATA_TIMESTAMP],
  1.8981 +      language:         message[PDU_CDMA_LANGUAGE_INDICATOR],
  1.8982 +      status:           null,
  1.8983 +      scts:             null,
  1.8984 +      dt:               null,
  1.8985 +      encoding:         message.encoding,
  1.8986 +      messageClass:     GECKO_SMS_MESSAGE_CLASSES[PDU_DCS_MSG_CLASS_NORMAL],
  1.8987 +      messageType:      message.messageType,
  1.8988 +      serviceCategory:  message.service,
  1.8989 +      subMsgType:       message[PDU_CDMA_MSG_USERDATA_MSG_ID].msgType,
  1.8990 +      msgId:            message[PDU_CDMA_MSG_USERDATA_MSG_ID].msgId,
  1.8991 +      errorClass:       message.errorClass,
  1.8992 +      msgStatus:        message.msgStatus,
  1.8993 +      teleservice:      message.teleservice
  1.8994 +    };
  1.8995 +
  1.8996 +    return msg;
  1.8997 +  },
  1.8998 +
  1.8999 +  /**
  1.9000 +   * Helper for processing received SMS parcel data.
  1.9001 +   *
  1.9002 +   * @param length
  1.9003 +   *        Length of SMS string in the incoming parcel.
  1.9004 +   *
  1.9005 +   * @return Message parsed or null for invalid message.
  1.9006 +   */
  1.9007 +  processReceivedSms: function(length) {
  1.9008 +    if (!length) {
  1.9009 +      if (DEBUG) this.context.debug("Received empty SMS!");
  1.9010 +      return [null, PDU_FCS_UNSPECIFIED];
  1.9011 +    }
  1.9012 +
  1.9013 +    let message = this.readMessage();
  1.9014 +    if (DEBUG) this.context.debug("Got new SMS: " + JSON.stringify(message));
  1.9015 +
  1.9016 +    // Determine result
  1.9017 +    if (!message) {
  1.9018 +      return [null, PDU_FCS_UNSPECIFIED];
  1.9019 +    }
  1.9020 +
  1.9021 +    return [message, PDU_FCS_OK];
  1.9022 +  },
  1.9023 +
  1.9024 +  /**
  1.9025 +   * Data readers
  1.9026 +   */
  1.9027 +  readInt: function() {
  1.9028 +    return this.context.Buf.readInt32();
  1.9029 +  },
  1.9030 +
  1.9031 +  readByte: function() {
  1.9032 +    return (this.context.Buf.readInt32() & 0xFF);
  1.9033 +  },
  1.9034 +
  1.9035 +  /**
  1.9036 +   * Decode CDMA address data into address string
  1.9037 +   *
  1.9038 +   * @see 3GGP2 C.S0015-B 2.0, 3.4.3.3 Address Parameters
  1.9039 +   *
  1.9040 +   * Required key in addrInfo
  1.9041 +   * @param addrLength
  1.9042 +   *        Length of address
  1.9043 +   * @param digitMode
  1.9044 +   *        Address encoding method
  1.9045 +   * @param address
  1.9046 +   *        Array of encoded address data
  1.9047 +   */
  1.9048 +  decodeAddr: function(addrInfo) {
  1.9049 +    let result = "";
  1.9050 +    for (let i = 0; i < addrInfo.addrLength; i++) {
  1.9051 +      if (addrInfo.digitMode === PDU_CDMA_MSG_ADDR_DIGIT_MODE_DTMF) {
  1.9052 +        result += this.dtmfChars.charAt(addrInfo.address[i]);
  1.9053 +      } else {
  1.9054 +        result += String.fromCharCode(addrInfo.address[i]);
  1.9055 +      }
  1.9056 +    }
  1.9057 +    return result;
  1.9058 +  },
  1.9059 +
  1.9060 +  /**
  1.9061 +   * Read userData in parcel buffer and decode into message object.
  1.9062 +   * Each subparameter will be stored in corresponding key.
  1.9063 +   *
  1.9064 +   * @see 3GGP2 C.S0015-B 2.0, 3.4.3.7 Bearer Data
  1.9065 +   *                           4.5 Bearer Data Parameters
  1.9066 +   */
  1.9067 +  decodeUserData: function(message) {
  1.9068 +    let userDataLength = this.readInt();
  1.9069 +
  1.9070 +    while (userDataLength > 0) {
  1.9071 +      let id = this.readByte(),
  1.9072 +          length = this.readByte(),
  1.9073 +          userDataBuffer = [];
  1.9074 +
  1.9075 +      for (let i = 0; i < length; i++) {
  1.9076 +          userDataBuffer.push(this.readByte());
  1.9077 +      }
  1.9078 +
  1.9079 +      this.context.BitBufferHelper.startRead(userDataBuffer);
  1.9080 +
  1.9081 +      switch (id) {
  1.9082 +        case PDU_CDMA_MSG_USERDATA_MSG_ID:
  1.9083 +          message[id] = this.decodeUserDataMsgId();
  1.9084 +          break;
  1.9085 +        case PDU_CDMA_MSG_USERDATA_BODY:
  1.9086 +          message[id] = this.decodeUserDataMsg(message[PDU_CDMA_MSG_USERDATA_MSG_ID].userHeader);
  1.9087 +          break;
  1.9088 +        case PDU_CDMA_MSG_USERDATA_TIMESTAMP:
  1.9089 +          message[id] = this.decodeUserDataTimestamp();
  1.9090 +          break;
  1.9091 +        case PDU_CDMA_MSG_USERDATA_REPLY_OPTION:
  1.9092 +          message[id] = this.decodeUserDataReplyOption();
  1.9093 +          break;
  1.9094 +        case PDU_CDMA_LANGUAGE_INDICATOR:
  1.9095 +          message[id] = this.decodeLanguageIndicator();
  1.9096 +          break;
  1.9097 +        case PDU_CDMA_MSG_USERDATA_CALLBACK_NUMBER:
  1.9098 +          message[id] = this.decodeUserDataCallbackNumber();
  1.9099 +          break;
  1.9100 +        case PDU_CDMA_MSG_USER_DATA_MSG_STATUS:
  1.9101 +          message[id] = this.decodeUserDataMsgStatus();
  1.9102 +          break;
  1.9103 +      }
  1.9104 +
  1.9105 +      userDataLength -= (length + 2);
  1.9106 +      userDataBuffer = [];
  1.9107 +    }
  1.9108 +  },
  1.9109 +
  1.9110 +  /**
  1.9111 +   * User data subparameter decoder: Message Identifier
  1.9112 +   *
  1.9113 +   * @see 3GGP2 C.S0015-B 2.0, 4.5.1 Message Identifier
  1.9114 +   */
  1.9115 +  decodeUserDataMsgId: function() {
  1.9116 +    let result = {};
  1.9117 +    let BitBufferHelper = this.context.BitBufferHelper;
  1.9118 +    result.msgType = BitBufferHelper.readBits(4);
  1.9119 +    result.msgId = BitBufferHelper.readBits(16);
  1.9120 +    result.userHeader = BitBufferHelper.readBits(1);
  1.9121 +
  1.9122 +    return result;
  1.9123 +  },
  1.9124 +
  1.9125 +  /**
  1.9126 +   * Decode user data header, we only care about segment information
  1.9127 +   * on CDMA.
  1.9128 +   *
  1.9129 +   * This function is mostly copied from gsmPduHelper.readUserDataHeader() but
  1.9130 +   * change the read function, because CDMA user header decoding is't byte-wise
  1.9131 +   * aligned.
  1.9132 +   */
  1.9133 +  decodeUserDataHeader: function(encoding) {
  1.9134 +    let BitBufferHelper = this.context.BitBufferHelper;
  1.9135 +    let header = {},
  1.9136 +        headerSize = BitBufferHelper.readBits(8),
  1.9137 +        userDataHeaderSize = headerSize + 1,
  1.9138 +        headerPaddingBits = 0;
  1.9139 +
  1.9140 +    // Calculate header size
  1.9141 +    if (encoding === PDU_DCS_MSG_CODING_7BITS_ALPHABET) {
  1.9142 +      // Length is in 7-bit
  1.9143 +      header.length = Math.ceil(userDataHeaderSize * 8 / 7);
  1.9144 +      // Calulate padding length
  1.9145 +      headerPaddingBits = (header.length * 7) - (userDataHeaderSize * 8);
  1.9146 +    } else if (encoding === PDU_DCS_MSG_CODING_8BITS_ALPHABET) {
  1.9147 +      header.length = userDataHeaderSize;
  1.9148 +    } else {
  1.9149 +      header.length = userDataHeaderSize / 2;
  1.9150 +    }
  1.9151 +
  1.9152 +    while (headerSize) {
  1.9153 +      let identifier = BitBufferHelper.readBits(8),
  1.9154 +          length = BitBufferHelper.readBits(8);
  1.9155 +
  1.9156 +      headerSize -= (2 + length);
  1.9157 +
  1.9158 +      switch (identifier) {
  1.9159 +        case PDU_IEI_CONCATENATED_SHORT_MESSAGES_8BIT: {
  1.9160 +          let ref = BitBufferHelper.readBits(8),
  1.9161 +              max = BitBufferHelper.readBits(8),
  1.9162 +              seq = BitBufferHelper.readBits(8);
  1.9163 +          if (max && seq && (seq <= max)) {
  1.9164 +            header.segmentRef = ref;
  1.9165 +            header.segmentMaxSeq = max;
  1.9166 +            header.segmentSeq = seq;
  1.9167 +          }
  1.9168 +          break;
  1.9169 +        }
  1.9170 +        case PDU_IEI_APPLICATION_PORT_ADDRESSING_SCHEME_8BIT: {
  1.9171 +          let dstp = BitBufferHelper.readBits(8),
  1.9172 +              orip = BitBufferHelper.readBits(8);
  1.9173 +          if ((dstp < PDU_APA_RESERVED_8BIT_PORTS)
  1.9174 +              || (orip < PDU_APA_RESERVED_8BIT_PORTS)) {
  1.9175 +            // 3GPP TS 23.040 clause 9.2.3.24.3: "A receiving entity shall
  1.9176 +            // ignore any information element where the value of the
  1.9177 +            // Information-Element-Data is Reserved or not supported"
  1.9178 +            break;
  1.9179 +          }
  1.9180 +          header.destinationPort = dstp;
  1.9181 +          header.originatorPort = orip;
  1.9182 +          break;
  1.9183 +        }
  1.9184 +        case PDU_IEI_APPLICATION_PORT_ADDRESSING_SCHEME_16BIT: {
  1.9185 +          let dstp = (BitBufferHelper.readBits(8) << 8) | BitBufferHelper.readBits(8),
  1.9186 +              orip = (BitBufferHelper.readBits(8) << 8) | BitBufferHelper.readBits(8);
  1.9187 +          // 3GPP TS 23.040 clause 9.2.3.24.4: "A receiving entity shall
  1.9188 +          // ignore any information element where the value of the
  1.9189 +          // Information-Element-Data is Reserved or not supported"
  1.9190 +          if ((dstp < PDU_APA_VALID_16BIT_PORTS)
  1.9191 +              && (orip < PDU_APA_VALID_16BIT_PORTS)) {
  1.9192 +            header.destinationPort = dstp;
  1.9193 +            header.originatorPort = orip;
  1.9194 +          }
  1.9195 +          break;
  1.9196 +        }
  1.9197 +        case PDU_IEI_CONCATENATED_SHORT_MESSAGES_16BIT: {
  1.9198 +          let ref = (BitBufferHelper.readBits(8) << 8) | BitBufferHelper.readBits(8),
  1.9199 +              max = BitBufferHelper.readBits(8),
  1.9200 +              seq = BitBufferHelper.readBits(8);
  1.9201 +          if (max && seq && (seq <= max)) {
  1.9202 +            header.segmentRef = ref;
  1.9203 +            header.segmentMaxSeq = max;
  1.9204 +            header.segmentSeq = seq;
  1.9205 +          }
  1.9206 +          break;
  1.9207 +        }
  1.9208 +        case PDU_IEI_NATIONAL_LANGUAGE_SINGLE_SHIFT: {
  1.9209 +          let langShiftIndex = BitBufferHelper.readBits(8);
  1.9210 +          if (langShiftIndex < PDU_NL_SINGLE_SHIFT_TABLES.length) {
  1.9211 +            header.langShiftIndex = langShiftIndex;
  1.9212 +          }
  1.9213 +          break;
  1.9214 +        }
  1.9215 +        case PDU_IEI_NATIONAL_LANGUAGE_LOCKING_SHIFT: {
  1.9216 +          let langIndex = BitBufferHelper.readBits(8);
  1.9217 +          if (langIndex < PDU_NL_LOCKING_SHIFT_TABLES.length) {
  1.9218 +            header.langIndex = langIndex;
  1.9219 +          }
  1.9220 +          break;
  1.9221 +        }
  1.9222 +        case PDU_IEI_SPECIAL_SMS_MESSAGE_INDICATION: {
  1.9223 +          let msgInd = BitBufferHelper.readBits(8) & 0xFF,
  1.9224 +              msgCount = BitBufferHelper.readBits(8);
  1.9225 +          /*
  1.9226 +           * TS 23.040 V6.8.1 Sec 9.2.3.24.2
  1.9227 +           * bits 1 0   : basic message indication type
  1.9228 +           * bits 4 3 2 : extended message indication type
  1.9229 +           * bits 6 5   : Profile id
  1.9230 +           * bit  7     : storage type
  1.9231 +           */
  1.9232 +          let storeType = msgInd & PDU_MWI_STORE_TYPE_BIT;
  1.9233 +          header.mwi = {};
  1.9234 +          mwi = header.mwi;
  1.9235 +
  1.9236 +          if (storeType == PDU_MWI_STORE_TYPE_STORE) {
  1.9237 +            // Store message because TP_UDH indicates so, note this may override
  1.9238 +            // the setting in DCS, but that is expected
  1.9239 +            mwi.discard = false;
  1.9240 +          } else if (mwi.discard === undefined) {
  1.9241 +            // storeType == PDU_MWI_STORE_TYPE_DISCARD
  1.9242 +            // only override mwi.discard here if it hasn't already been set
  1.9243 +            mwi.discard = true;
  1.9244 +          }
  1.9245 +
  1.9246 +          mwi.msgCount = msgCount & 0xFF;
  1.9247 +          mwi.active = mwi.msgCount > 0;
  1.9248 +
  1.9249 +          if (DEBUG) {
  1.9250 +            this.context.debug("MWI in TP_UDH received: " + JSON.stringify(mwi));
  1.9251 +          }
  1.9252 +          break;
  1.9253 +        }
  1.9254 +        default:
  1.9255 +          // Drop unsupported id
  1.9256 +          for (let i = 0; i < length; i++) {
  1.9257 +            BitBufferHelper.readBits(8);
  1.9258 +          }
  1.9259 +      }
  1.9260 +    }
  1.9261 +
  1.9262 +    // Consume padding bits
  1.9263 +    if (headerPaddingBits) {
  1.9264 +      BitBufferHelper.readBits(headerPaddingBits);
  1.9265 +    }
  1.9266 +
  1.9267 +    return header;
  1.9268 +  },
  1.9269 +
  1.9270 +  getCdmaMsgEncoding: function(encoding) {
  1.9271 +    // Determine encoding method
  1.9272 +    switch (encoding) {
  1.9273 +      case PDU_CDMA_MSG_CODING_7BITS_ASCII:
  1.9274 +      case PDU_CDMA_MSG_CODING_IA5:
  1.9275 +      case PDU_CDMA_MSG_CODING_7BITS_GSM:
  1.9276 +        return PDU_DCS_MSG_CODING_7BITS_ALPHABET;
  1.9277 +      case PDU_CDMA_MSG_CODING_OCTET:
  1.9278 +      case PDU_CDMA_MSG_CODING_IS_91:
  1.9279 +      case PDU_CDMA_MSG_CODING_LATIN_HEBREW:
  1.9280 +      case PDU_CDMA_MSG_CODING_LATIN:
  1.9281 +        return PDU_DCS_MSG_CODING_8BITS_ALPHABET;
  1.9282 +      case PDU_CDMA_MSG_CODING_UNICODE:
  1.9283 +      case PDU_CDMA_MSG_CODING_SHIFT_JIS:
  1.9284 +      case PDU_CDMA_MSG_CODING_KOREAN:
  1.9285 +        return PDU_DCS_MSG_CODING_16BITS_ALPHABET;
  1.9286 +    }
  1.9287 +    return null;
  1.9288 +  },
  1.9289 +
  1.9290 +  decodeCdmaPDUMsg: function(encoding, msgType, msgBodySize) {
  1.9291 +    const langTable = PDU_NL_LOCKING_SHIFT_TABLES[PDU_NL_IDENTIFIER_DEFAULT];
  1.9292 +    const langShiftTable = PDU_NL_SINGLE_SHIFT_TABLES[PDU_NL_IDENTIFIER_DEFAULT];
  1.9293 +    let BitBufferHelper = this.context.BitBufferHelper;
  1.9294 +    let result = "";
  1.9295 +    let msgDigit;
  1.9296 +    switch (encoding) {
  1.9297 +      case PDU_CDMA_MSG_CODING_OCTET:         // TODO : Require Test
  1.9298 +        while(msgBodySize > 0) {
  1.9299 +          msgDigit = String.fromCharCode(BitBufferHelper.readBits(8));
  1.9300 +          result += msgDigit;
  1.9301 +          msgBodySize--;
  1.9302 +        }
  1.9303 +        break;
  1.9304 +      case PDU_CDMA_MSG_CODING_IS_91:         // TODO : Require Test
  1.9305 +        // Referenced from android code
  1.9306 +        switch (msgType) {
  1.9307 +          case PDU_CDMA_MSG_CODING_IS_91_TYPE_SMS:
  1.9308 +          case PDU_CDMA_MSG_CODING_IS_91_TYPE_SMS_FULL:
  1.9309 +          case PDU_CDMA_MSG_CODING_IS_91_TYPE_VOICEMAIL_STATUS:
  1.9310 +            while(msgBodySize > 0) {
  1.9311 +              msgDigit = String.fromCharCode(BitBufferHelper.readBits(6) + 0x20);
  1.9312 +              result += msgDigit;
  1.9313 +              msgBodySize--;
  1.9314 +            }
  1.9315 +            break;
  1.9316 +          case PDU_CDMA_MSG_CODING_IS_91_TYPE_CLI:
  1.9317 +            let addrInfo = {};
  1.9318 +            addrInfo.digitMode = PDU_CDMA_MSG_ADDR_DIGIT_MODE_DTMF;
  1.9319 +            addrInfo.numberMode = PDU_CDMA_MSG_ADDR_NUMBER_MODE_ANSI;
  1.9320 +            addrInfo.numberType = PDU_CDMA_MSG_ADDR_NUMBER_TYPE_UNKNOWN;
  1.9321 +            addrInfo.numberPlan = PDU_CDMA_MSG_ADDR_NUMBER_PLAN_UNKNOWN;
  1.9322 +            addrInfo.addrLength = msgBodySize;
  1.9323 +            addrInfo.address = [];
  1.9324 +            for (let i = 0; i < addrInfo.addrLength; i++) {
  1.9325 +              addrInfo.address.push(BitBufferHelper.readBits(4));
  1.9326 +            }
  1.9327 +            result = this.decodeAddr(addrInfo);
  1.9328 +            break;
  1.9329 +        }
  1.9330 +        // Fall through.
  1.9331 +      case PDU_CDMA_MSG_CODING_7BITS_ASCII:
  1.9332 +      case PDU_CDMA_MSG_CODING_IA5:           // TODO : Require Test
  1.9333 +        while(msgBodySize > 0) {
  1.9334 +          msgDigit = BitBufferHelper.readBits(7);
  1.9335 +          if (msgDigit >= 32) {
  1.9336 +            msgDigit = String.fromCharCode(msgDigit);
  1.9337 +          } else {
  1.9338 +            if (msgDigit !== PDU_NL_EXTENDED_ESCAPE) {
  1.9339 +              msgDigit = langTable[msgDigit];
  1.9340 +            } else {
  1.9341 +              msgDigit = BitBufferHelper.readBits(7);
  1.9342 +              msgBodySize--;
  1.9343 +              msgDigit = langShiftTable[msgDigit];
  1.9344 +            }
  1.9345 +          }
  1.9346 +          result += msgDigit;
  1.9347 +          msgBodySize--;
  1.9348 +        }
  1.9349 +        break;
  1.9350 +      case PDU_CDMA_MSG_CODING_UNICODE:
  1.9351 +        while(msgBodySize > 0) {
  1.9352 +          msgDigit = String.fromCharCode(BitBufferHelper.readBits(16));
  1.9353 +          result += msgDigit;
  1.9354 +          msgBodySize--;
  1.9355 +        }
  1.9356 +        break;
  1.9357 +      case PDU_CDMA_MSG_CODING_7BITS_GSM:     // TODO : Require Test
  1.9358 +        while(msgBodySize > 0) {
  1.9359 +          msgDigit = BitBufferHelper.readBits(7);
  1.9360 +          if (msgDigit !== PDU_NL_EXTENDED_ESCAPE) {
  1.9361 +            msgDigit = langTable[msgDigit];
  1.9362 +          } else {
  1.9363 +            msgDigit = BitBufferHelper.readBits(7);
  1.9364 +            msgBodySize--;
  1.9365 +            msgDigit = langShiftTable[msgDigit];
  1.9366 +          }
  1.9367 +          result += msgDigit;
  1.9368 +          msgBodySize--;
  1.9369 +        }
  1.9370 +        break;
  1.9371 +      case PDU_CDMA_MSG_CODING_LATIN:         // TODO : Require Test
  1.9372 +        // Reference : http://en.wikipedia.org/wiki/ISO/IEC_8859-1
  1.9373 +        while(msgBodySize > 0) {
  1.9374 +          msgDigit = String.fromCharCode(BitBufferHelper.readBits(8));
  1.9375 +          result += msgDigit;
  1.9376 +          msgBodySize--;
  1.9377 +        }
  1.9378 +        break;
  1.9379 +      case PDU_CDMA_MSG_CODING_LATIN_HEBREW:  // TODO : Require Test
  1.9380 +        // Reference : http://en.wikipedia.org/wiki/ISO/IEC_8859-8
  1.9381 +        while(msgBodySize > 0) {
  1.9382 +          msgDigit = BitBufferHelper.readBits(8);
  1.9383 +          if (msgDigit === 0xDF) {
  1.9384 +            msgDigit = String.fromCharCode(0x2017);
  1.9385 +          } else if (msgDigit === 0xFD) {
  1.9386 +            msgDigit = String.fromCharCode(0x200E);
  1.9387 +          } else if (msgDigit === 0xFE) {
  1.9388 +            msgDigit = String.fromCharCode(0x200F);
  1.9389 +          } else if (msgDigit >= 0xE0 && msgDigit <= 0xFA) {
  1.9390 +            msgDigit = String.fromCharCode(0x4F0 + msgDigit);
  1.9391 +          } else {
  1.9392 +            msgDigit = String.fromCharCode(msgDigit);
  1.9393 +          }
  1.9394 +          result += msgDigit;
  1.9395 +          msgBodySize--;
  1.9396 +        }
  1.9397 +        break;
  1.9398 +      case PDU_CDMA_MSG_CODING_SHIFT_JIS:
  1.9399 +        // Reference : http://msdn.microsoft.com/en-US/goglobal/cc305152.aspx
  1.9400 +        //             http://demo.icu-project.org/icu-bin/convexp?conv=Shift_JIS
  1.9401 +        let shift_jis_message = [];
  1.9402 +
  1.9403 +        while (msgBodySize > 0) {
  1.9404 +          shift_jis_message.push(BitBufferHelper.readBits(8));
  1.9405 +          msgBodySize--;
  1.9406 +        }
  1.9407 +
  1.9408 +        let decoder = new TextDecoder("shift_jis");
  1.9409 +        result = decoder.decode(new Uint8Array(shift_jis_message));
  1.9410 +        break;
  1.9411 +      case PDU_CDMA_MSG_CODING_KOREAN:
  1.9412 +      case PDU_CDMA_MSG_CODING_GSM_DCS:
  1.9413 +        // Fall through.
  1.9414 +      default:
  1.9415 +        break;
  1.9416 +    }
  1.9417 +    return result;
  1.9418 +  },
  1.9419 +
  1.9420 +  /**
  1.9421 +   * User data subparameter decoder : User Data
  1.9422 +   *
  1.9423 +   * @see 3GGP2 C.S0015-B 2.0, 4.5.2 User Data
  1.9424 +   */
  1.9425 +  decodeUserDataMsg: function(hasUserHeader) {
  1.9426 +    let BitBufferHelper = this.context.BitBufferHelper;
  1.9427 +    let result = {},
  1.9428 +        encoding = BitBufferHelper.readBits(5),
  1.9429 +        msgType;
  1.9430 +
  1.9431 +    if (encoding === PDU_CDMA_MSG_CODING_IS_91) {
  1.9432 +      msgType = BitBufferHelper.readBits(8);
  1.9433 +    }
  1.9434 +    result.encoding = this.getCdmaMsgEncoding(encoding);
  1.9435 +
  1.9436 +    let msgBodySize = BitBufferHelper.readBits(8);
  1.9437 +
  1.9438 +    // For segmented SMS, a user header is included before sms content
  1.9439 +    if (hasUserHeader) {
  1.9440 +      result.header = this.decodeUserDataHeader(result.encoding);
  1.9441 +      // header size is included in body size, they are decoded
  1.9442 +      msgBodySize -= result.header.length;
  1.9443 +    }
  1.9444 +
  1.9445 +    // Store original payload if enconding is OCTET for further handling of WAP Push, etc.
  1.9446 +    if (encoding === PDU_CDMA_MSG_CODING_OCTET && msgBodySize > 0) {
  1.9447 +      result.data = new Uint8Array(msgBodySize);
  1.9448 +      for (let i = 0; i < msgBodySize; i++) {
  1.9449 +        result.data[i] = BitBufferHelper.readBits(8);
  1.9450 +      }
  1.9451 +      BitBufferHelper.backwardReadPilot(8 * msgBodySize);
  1.9452 +    }
  1.9453 +
  1.9454 +    // Decode sms content
  1.9455 +    result.body = this.decodeCdmaPDUMsg(encoding, msgType, msgBodySize);
  1.9456 +
  1.9457 +    return result;
  1.9458 +  },
  1.9459 +
  1.9460 +  decodeBcd: function(value) {
  1.9461 +    return ((value >> 4) & 0xF) * 10 + (value & 0x0F);
  1.9462 +  },
  1.9463 +
  1.9464 +  /**
  1.9465 +   * User data subparameter decoder : Time Stamp
  1.9466 +   *
  1.9467 +   * @see 3GGP2 C.S0015-B 2.0, 4.5.4 Message Center Time Stamp
  1.9468 +   */
  1.9469 +  decodeUserDataTimestamp: function() {
  1.9470 +    let BitBufferHelper = this.context.BitBufferHelper;
  1.9471 +    let year = this.decodeBcd(BitBufferHelper.readBits(8)),
  1.9472 +        month = this.decodeBcd(BitBufferHelper.readBits(8)) - 1,
  1.9473 +        date = this.decodeBcd(BitBufferHelper.readBits(8)),
  1.9474 +        hour = this.decodeBcd(BitBufferHelper.readBits(8)),
  1.9475 +        min = this.decodeBcd(BitBufferHelper.readBits(8)),
  1.9476 +        sec = this.decodeBcd(BitBufferHelper.readBits(8));
  1.9477 +
  1.9478 +    if (year >= 96 && year <= 99) {
  1.9479 +      year += 1900;
  1.9480 +    } else {
  1.9481 +      year += 2000;
  1.9482 +    }
  1.9483 +
  1.9484 +    let result = (new Date(year, month, date, hour, min, sec, 0)).valueOf();
  1.9485 +
  1.9486 +    return result;
  1.9487 +  },
  1.9488 +
  1.9489 +  /**
  1.9490 +   * User data subparameter decoder : Reply Option
  1.9491 +   *
  1.9492 +   * @see 3GGP2 C.S0015-B 2.0, 4.5.11 Reply Option
  1.9493 +   */
  1.9494 +  decodeUserDataReplyOption: function() {
  1.9495 +    let replyAction = this.context.BitBufferHelper.readBits(4),
  1.9496 +        result = { userAck: (replyAction & 0x8) ? true : false,
  1.9497 +                   deliverAck: (replyAction & 0x4) ? true : false,
  1.9498 +                   readAck: (replyAction & 0x2) ? true : false,
  1.9499 +                   report: (replyAction & 0x1) ? true : false
  1.9500 +                 };
  1.9501 +
  1.9502 +    return result;
  1.9503 +  },
  1.9504 +
  1.9505 +  /**
  1.9506 +   * User data subparameter decoder : Language Indicator
  1.9507 +   *
  1.9508 +   * @see 3GGP2 C.S0015-B 2.0, 4.5.14 Language Indicator
  1.9509 +   */
  1.9510 +  decodeLanguageIndicator: function() {
  1.9511 +    let language = this.context.BitBufferHelper.readBits(8);
  1.9512 +    let result = CB_CDMA_LANG_GROUP[language];
  1.9513 +    return result;
  1.9514 +  },
  1.9515 +
  1.9516 +  /**
  1.9517 +   * User data subparameter decoder : Call-Back Number
  1.9518 +   *
  1.9519 +   * @see 3GGP2 C.S0015-B 2.0, 4.5.15 Call-Back Number
  1.9520 +   */
  1.9521 +  decodeUserDataCallbackNumber: function() {
  1.9522 +    let BitBufferHelper = this.context.BitBufferHelper;
  1.9523 +    let digitMode = BitBufferHelper.readBits(1);
  1.9524 +    if (digitMode) {
  1.9525 +      let numberType = BitBufferHelper.readBits(3),
  1.9526 +          numberPlan = BitBufferHelper.readBits(4);
  1.9527 +    }
  1.9528 +    let numberFields = BitBufferHelper.readBits(8),
  1.9529 +        result = "";
  1.9530 +    for (let i = 0; i < numberFields; i++) {
  1.9531 +      if (digitMode === PDU_CDMA_MSG_ADDR_DIGIT_MODE_DTMF) {
  1.9532 +        let addrDigit = BitBufferHelper.readBits(4);
  1.9533 +        result += this.dtmfChars.charAt(addrDigit);
  1.9534 +      } else {
  1.9535 +        let addrDigit = BitBufferHelper.readBits(8);
  1.9536 +        result += String.fromCharCode(addrDigit);
  1.9537 +      }
  1.9538 +    }
  1.9539 +
  1.9540 +    return result;
  1.9541 +  },
  1.9542 +
  1.9543 +  /**
  1.9544 +   * User data subparameter decoder : Message Status
  1.9545 +   *
  1.9546 +   * @see 3GGP2 C.S0015-B 2.0, 4.5.21 Message Status
  1.9547 +   */
  1.9548 +  decodeUserDataMsgStatus: function() {
  1.9549 +    let BitBufferHelper = this.context.BitBufferHelper;
  1.9550 +    let result = {
  1.9551 +      errorClass: BitBufferHelper.readBits(2),
  1.9552 +      msgStatus: BitBufferHelper.readBits(6)
  1.9553 +    };
  1.9554 +
  1.9555 +    return result;
  1.9556 +  },
  1.9557 +
  1.9558 +  /**
  1.9559 +   * Decode information record parcel.
  1.9560 +   */
  1.9561 +  decodeInformationRecord: function() {
  1.9562 +    let Buf = this.context.Buf;
  1.9563 +    let record = {};
  1.9564 +    let numOfRecords = Buf.readInt32();
  1.9565 +
  1.9566 +    let type;
  1.9567 +    for (let i = 0; i < numOfRecords; i++) {
  1.9568 +      type = Buf.readInt32();
  1.9569 +
  1.9570 +      switch (type) {
  1.9571 +        /*
  1.9572 +         * Every type is encaped by ril, except extended display
  1.9573 +         */
  1.9574 +        case PDU_CDMA_INFO_REC_TYPE_DISPLAY:
  1.9575 +          record.display = Buf.readString();
  1.9576 +          break;
  1.9577 +        case PDU_CDMA_INFO_REC_TYPE_CALLED_PARTY_NUMBER:
  1.9578 +          record.calledNumber = {};
  1.9579 +          record.calledNumber.number = Buf.readString();
  1.9580 +          record.calledNumber.type = Buf.readInt32();
  1.9581 +          record.calledNumber.plan = Buf.readInt32();
  1.9582 +          record.calledNumber.pi = Buf.readInt32();
  1.9583 +          record.calledNumber.si = Buf.readInt32();
  1.9584 +          break;
  1.9585 +        case PDU_CDMA_INFO_REC_TYPE_CALLING_PARTY_NUMBER:
  1.9586 +          record.callingNumber = {};
  1.9587 +          record.callingNumber.number = Buf.readString();
  1.9588 +          record.callingNumber.type = Buf.readInt32();
  1.9589 +          record.callingNumber.plan = Buf.readInt32();
  1.9590 +          record.callingNumber.pi = Buf.readInt32();
  1.9591 +          record.callingNumber.si = Buf.readInt32();
  1.9592 +          break;
  1.9593 +        case PDU_CDMA_INFO_REC_TYPE_CONNECTED_NUMBER:
  1.9594 +          record.connectedNumber = {};
  1.9595 +          record.connectedNumber.number = Buf.readString();
  1.9596 +          record.connectedNumber.type = Buf.readInt32();
  1.9597 +          record.connectedNumber.plan = Buf.readInt32();
  1.9598 +          record.connectedNumber.pi = Buf.readInt32();
  1.9599 +          record.connectedNumber.si = Buf.readInt32();
  1.9600 +          break;
  1.9601 +        case PDU_CDMA_INFO_REC_TYPE_SIGNAL:
  1.9602 +          record.signal = {};
  1.9603 +          record.signal.present = Buf.readInt32();
  1.9604 +          record.signal.type = Buf.readInt32();
  1.9605 +          record.signal.alertPitch = Buf.readInt32();
  1.9606 +          record.signal.signal = Buf.readInt32();
  1.9607 +          break;
  1.9608 +        case PDU_CDMA_INFO_REC_TYPE_REDIRECTING_NUMBER:
  1.9609 +          record.redirect = {};
  1.9610 +          record.redirect.number = Buf.readString();
  1.9611 +          record.redirect.type = Buf.readInt32();
  1.9612 +          record.redirect.plan = Buf.readInt32();
  1.9613 +          record.redirect.pi = Buf.readInt32();
  1.9614 +          record.redirect.si = Buf.readInt32();
  1.9615 +          record.redirect.reason = Buf.readInt32();
  1.9616 +          break;
  1.9617 +        case PDU_CDMA_INFO_REC_TYPE_LINE_CONTROL:
  1.9618 +          record.lineControl = {};
  1.9619 +          record.lineControl.polarityIncluded = Buf.readInt32();
  1.9620 +          record.lineControl.toggle = Buf.readInt32();
  1.9621 +          record.lineControl.recerse = Buf.readInt32();
  1.9622 +          record.lineControl.powerDenial = Buf.readInt32();
  1.9623 +          break;
  1.9624 +        case PDU_CDMA_INFO_REC_TYPE_EXTENDED_DISPLAY:
  1.9625 +          let length = Buf.readInt32();
  1.9626 +          /*
  1.9627 +           * Extended display is still in format defined in
  1.9628 +           * C.S0005-F v1.0, 3.7.5.16
  1.9629 +           */
  1.9630 +          record.extendedDisplay = {};
  1.9631 +
  1.9632 +          let headerByte = Buf.readInt32();
  1.9633 +          length--;
  1.9634 +          // Based on spec, headerByte must be 0x80 now
  1.9635 +          record.extendedDisplay.indicator = (headerByte >> 7);
  1.9636 +          record.extendedDisplay.type = (headerByte & 0x7F);
  1.9637 +          record.extendedDisplay.records = [];
  1.9638 +
  1.9639 +          while (length > 0) {
  1.9640 +            let display = {};
  1.9641 +
  1.9642 +            display.tag = Buf.readInt32();
  1.9643 +            length--;
  1.9644 +            if (display.tag !== INFO_REC_EXTENDED_DISPLAY_BLANK &&
  1.9645 +                display.tag !== INFO_REC_EXTENDED_DISPLAY_SKIP) {
  1.9646 +              display.content = Buf.readString();
  1.9647 +              length -= (display.content.length + 1);
  1.9648 +            }
  1.9649 +
  1.9650 +            record.extendedDisplay.records.push(display);
  1.9651 +          }
  1.9652 +          break;
  1.9653 +        case PDU_CDMA_INFO_REC_TYPE_T53_CLIR:
  1.9654 +          record.cause = Buf.readInt32();
  1.9655 +          break;
  1.9656 +        case PDU_CDMA_INFO_REC_TYPE_T53_AUDIO_CONTROL:
  1.9657 +          record.audioControl = {};
  1.9658 +          record.audioControl.upLink = Buf.readInt32();
  1.9659 +          record.audioControl.downLink = Buf.readInt32();
  1.9660 +          break;
  1.9661 +        case PDU_CDMA_INFO_REC_TYPE_T53_RELEASE:
  1.9662 +          // Fall through
  1.9663 +        default:
  1.9664 +          throw new Error("UNSOLICITED_CDMA_INFO_REC(), Unsupported information record type " + record.type + "\n");
  1.9665 +      }
  1.9666 +    }
  1.9667 +
  1.9668 +    return record;
  1.9669 +  }
  1.9670 +};
  1.9671 +
  1.9672 +/**
  1.9673 + * Helper for processing ICC PDUs.
  1.9674 + */
  1.9675 +function ICCPDUHelperObject(aContext) {
  1.9676 +  this.context = aContext;
  1.9677 +}
  1.9678 +ICCPDUHelperObject.prototype = {
  1.9679 +  context: null,
  1.9680 +
  1.9681 +  /**
  1.9682 +   * Read GSM 8-bit unpacked octets,
  1.9683 +   * which are default 7-bit alphabets with bit 8 set to 0.
  1.9684 +   *
  1.9685 +   * @param numOctets
  1.9686 +   *        Number of octets to be read.
  1.9687 +   */
  1.9688 +  read8BitUnpackedToString: function(numOctets) {
  1.9689 +    let GsmPDUHelper = this.context.GsmPDUHelper;
  1.9690 +
  1.9691 +    let ret = "";
  1.9692 +    let escapeFound = false;
  1.9693 +    let i;
  1.9694 +    const langTable = PDU_NL_LOCKING_SHIFT_TABLES[PDU_NL_IDENTIFIER_DEFAULT];
  1.9695 +    const langShiftTable = PDU_NL_SINGLE_SHIFT_TABLES[PDU_NL_IDENTIFIER_DEFAULT];
  1.9696 +
  1.9697 +    for(i = 0; i < numOctets; i++) {
  1.9698 +      let octet = GsmPDUHelper.readHexOctet();
  1.9699 +      if (octet == 0xff) {
  1.9700 +        i++;
  1.9701 +        break;
  1.9702 +      }
  1.9703 +
  1.9704 +      if (escapeFound) {
  1.9705 +        escapeFound = false;
  1.9706 +        if (octet == PDU_NL_EXTENDED_ESCAPE) {
  1.9707 +          // According to 3GPP TS 23.038, section 6.2.1.1, NOTE 1, "On
  1.9708 +          // receipt of this code, a receiving entity shall display a space
  1.9709 +          // until another extensiion table is defined."
  1.9710 +          ret += " ";
  1.9711 +        } else if (octet == PDU_NL_RESERVED_CONTROL) {
  1.9712 +          // According to 3GPP TS 23.038 B.2, "This code represents a control
  1.9713 +          // character and therefore must not be used for language specific
  1.9714 +          // characters."
  1.9715 +          ret += " ";
  1.9716 +        } else {
  1.9717 +          ret += langShiftTable[octet];
  1.9718 +        }
  1.9719 +      } else if (octet == PDU_NL_EXTENDED_ESCAPE) {
  1.9720 +        escapeFound = true;
  1.9721 +      } else {
  1.9722 +        ret += langTable[octet];
  1.9723 +      }
  1.9724 +    }
  1.9725 +
  1.9726 +    let Buf = this.context.Buf;
  1.9727 +    Buf.seekIncoming((numOctets - i) * Buf.PDU_HEX_OCTET_SIZE);
  1.9728 +    return ret;
  1.9729 +  },
  1.9730 +
  1.9731 +  /**
  1.9732 +   * Write GSM 8-bit unpacked octets.
  1.9733 +   *
  1.9734 +   * @param numOctets   Number of total octets to be writen, including trailing
  1.9735 +   *                    0xff.
  1.9736 +   * @param str         String to be written. Could be null.
  1.9737 +   */
  1.9738 +  writeStringTo8BitUnpacked: function(numOctets, str) {
  1.9739 +    const langTable = PDU_NL_LOCKING_SHIFT_TABLES[PDU_NL_IDENTIFIER_DEFAULT];
  1.9740 +    const langShiftTable = PDU_NL_SINGLE_SHIFT_TABLES[PDU_NL_IDENTIFIER_DEFAULT];
  1.9741 +
  1.9742 +    let GsmPDUHelper = this.context.GsmPDUHelper;
  1.9743 +
  1.9744 +    // If the character is GSM extended alphabet, two octets will be written.
  1.9745 +    // So we need to keep track of number of octets to be written.
  1.9746 +    let i, j;
  1.9747 +    let len = str ? str.length : 0;
  1.9748 +    for (i = 0, j = 0; i < len && j < numOctets; i++) {
  1.9749 +      let c = str.charAt(i);
  1.9750 +      let octet = langTable.indexOf(c);
  1.9751 +
  1.9752 +      if (octet == -1) {
  1.9753 +        // Make sure we still have enough space to write two octets.
  1.9754 +        if (j + 2 > numOctets) {
  1.9755 +          break;
  1.9756 +        }
  1.9757 +
  1.9758 +        octet = langShiftTable.indexOf(c);
  1.9759 +        if (octet == -1) {
  1.9760 +          // Fallback to ASCII space.
  1.9761 +          octet = langTable.indexOf(' ');
  1.9762 +        }
  1.9763 +        GsmPDUHelper.writeHexOctet(PDU_NL_EXTENDED_ESCAPE);
  1.9764 +        j++;
  1.9765 +      }
  1.9766 +      GsmPDUHelper.writeHexOctet(octet);
  1.9767 +      j++;
  1.9768 +    }
  1.9769 +
  1.9770 +    // trailing 0xff
  1.9771 +    while (j++ < numOctets) {
  1.9772 +      GsmPDUHelper.writeHexOctet(0xff);
  1.9773 +    }
  1.9774 +  },
  1.9775 +
  1.9776 +  /**
  1.9777 +   * Read UCS2 String on UICC.
  1.9778 +   *
  1.9779 +   * @see TS 101.221, Annex A.
  1.9780 +   * @param scheme
  1.9781 +   *        Coding scheme for UCS2 on UICC. One of 0x80, 0x81 or 0x82.
  1.9782 +   * @param numOctets
  1.9783 +   *        Number of octets to be read as UCS2 string.
  1.9784 +   */
  1.9785 +  readICCUCS2String: function(scheme, numOctets) {
  1.9786 +    let Buf = this.context.Buf;
  1.9787 +    let GsmPDUHelper = this.context.GsmPDUHelper;
  1.9788 +
  1.9789 +    let str = "";
  1.9790 +    switch (scheme) {
  1.9791 +      /**
  1.9792 +       * +------+---------+---------+---------+---------+------+------+
  1.9793 +       * | 0x80 | Ch1_msb | Ch1_lsb | Ch2_msb | Ch2_lsb | 0xff | 0xff |
  1.9794 +       * +------+---------+---------+---------+---------+------+------+
  1.9795 +       */
  1.9796 +      case 0x80:
  1.9797 +        let isOdd = numOctets % 2;
  1.9798 +        let i;
  1.9799 +        for (i = 0; i < numOctets - isOdd; i += 2) {
  1.9800 +          let code = (GsmPDUHelper.readHexOctet() << 8) | GsmPDUHelper.readHexOctet();
  1.9801 +          if (code == 0xffff) {
  1.9802 +            i += 2;
  1.9803 +            break;
  1.9804 +          }
  1.9805 +          str += String.fromCharCode(code);
  1.9806 +        }
  1.9807 +
  1.9808 +        // Skip trailing 0xff
  1.9809 +        Buf.seekIncoming((numOctets - i) * Buf.PDU_HEX_OCTET_SIZE);
  1.9810 +        break;
  1.9811 +      case 0x81: // Fall through
  1.9812 +      case 0x82:
  1.9813 +        /**
  1.9814 +         * +------+-----+--------+-----+-----+-----+--------+------+
  1.9815 +         * | 0x81 | len | offset | Ch1 | Ch2 | ... | Ch_len | 0xff |
  1.9816 +         * +------+-----+--------+-----+-----+-----+--------+------+
  1.9817 +         *
  1.9818 +         * len : The length of characters.
  1.9819 +         * offset : 0hhh hhhh h000 0000
  1.9820 +         * Ch_n: bit 8 = 0
  1.9821 +         *       GSM default alphabets
  1.9822 +         *       bit 8 = 1
  1.9823 +         *       UCS2 character whose char code is (Ch_n & 0x7f) + offset
  1.9824 +         *
  1.9825 +         * +------+-----+------------+------------+-----+-----+-----+--------+
  1.9826 +         * | 0x82 | len | offset_msb | offset_lsb | Ch1 | Ch2 | ... | Ch_len |
  1.9827 +         * +------+-----+------------+------------+-----+-----+-----+--------+
  1.9828 +         *
  1.9829 +         * len : The length of characters.
  1.9830 +         * offset_msb, offset_lsn: offset
  1.9831 +         * Ch_n: bit 8 = 0
  1.9832 +         *       GSM default alphabets
  1.9833 +         *       bit 8 = 1
  1.9834 +         *       UCS2 character whose char code is (Ch_n & 0x7f) + offset
  1.9835 +         */
  1.9836 +        let len = GsmPDUHelper.readHexOctet();
  1.9837 +        let offset, headerLen;
  1.9838 +        if (scheme == 0x81) {
  1.9839 +          offset = GsmPDUHelper.readHexOctet() << 7;
  1.9840 +          headerLen = 2;
  1.9841 +        } else {
  1.9842 +          offset = (GsmPDUHelper.readHexOctet() << 8) | GsmPDUHelper.readHexOctet();
  1.9843 +          headerLen = 3;
  1.9844 +        }
  1.9845 +
  1.9846 +        for (let i = 0; i < len; i++) {
  1.9847 +          let ch = GsmPDUHelper.readHexOctet();
  1.9848 +          if (ch & 0x80) {
  1.9849 +            // UCS2
  1.9850 +            str += String.fromCharCode((ch & 0x7f) + offset);
  1.9851 +          } else {
  1.9852 +            // GSM 8bit
  1.9853 +            let count = 0, gotUCS2 = 0;
  1.9854 +            while ((i + count + 1 < len)) {
  1.9855 +              count++;
  1.9856 +              if (GsmPDUHelper.readHexOctet() & 0x80) {
  1.9857 +                gotUCS2 = 1;
  1.9858 +                break;
  1.9859 +              }
  1.9860 +            }
  1.9861 +            // Unread.
  1.9862 +            // +1 for the GSM alphabet indexed at i,
  1.9863 +            Buf.seekIncoming(-1 * (count + 1) * Buf.PDU_HEX_OCTET_SIZE);
  1.9864 +            str += this.read8BitUnpackedToString(count + 1 - gotUCS2);
  1.9865 +            i += count - gotUCS2;
  1.9866 +          }
  1.9867 +        }
  1.9868 +
  1.9869 +        // Skipping trailing 0xff
  1.9870 +        Buf.seekIncoming((numOctets - len - headerLen) * Buf.PDU_HEX_OCTET_SIZE);
  1.9871 +        break;
  1.9872 +    }
  1.9873 +    return str;
  1.9874 +  },
  1.9875 +
  1.9876 +  /**
  1.9877 +   * Read Alpha Id and Dialling number from TS TS 151.011 clause 10.5.1
  1.9878 +   *
  1.9879 +   * @param recordSize  The size of linear fixed record.
  1.9880 +   */
  1.9881 +  readAlphaIdDiallingNumber: function(recordSize) {
  1.9882 +    let Buf = this.context.Buf;
  1.9883 +    let length = Buf.readInt32();
  1.9884 +
  1.9885 +    let alphaLen = recordSize - ADN_FOOTER_SIZE_BYTES;
  1.9886 +    let alphaId = this.readAlphaIdentifier(alphaLen);
  1.9887 +
  1.9888 +    let number = this.readNumberWithLength();
  1.9889 +
  1.9890 +    // Skip 2 unused octets, CCP and EXT1.
  1.9891 +    Buf.seekIncoming(2 * Buf.PDU_HEX_OCTET_SIZE);
  1.9892 +    Buf.readStringDelimiter(length);
  1.9893 +
  1.9894 +    let contact = null;
  1.9895 +    if (alphaId || number) {
  1.9896 +      contact = {alphaId: alphaId,
  1.9897 +                 number: number};
  1.9898 +    }
  1.9899 +    return contact;
  1.9900 +  },
  1.9901 +
  1.9902 +  /**
  1.9903 +   * Write Alpha Identifier and Dialling number from TS 151.011 clause 10.5.1
  1.9904 +   *
  1.9905 +   * @param recordSize  The size of linear fixed record.
  1.9906 +   * @param alphaId     Alpha Identifier to be written.
  1.9907 +   * @param number      Dialling Number to be written.
  1.9908 +   */
  1.9909 +  writeAlphaIdDiallingNumber: function(recordSize, alphaId, number) {
  1.9910 +    let Buf = this.context.Buf;
  1.9911 +    let GsmPDUHelper = this.context.GsmPDUHelper;
  1.9912 +
  1.9913 +    // Write String length
  1.9914 +    let strLen = recordSize * 2;
  1.9915 +    Buf.writeInt32(strLen);
  1.9916 +
  1.9917 +    let alphaLen = recordSize - ADN_FOOTER_SIZE_BYTES;
  1.9918 +    this.writeAlphaIdentifier(alphaLen, alphaId);
  1.9919 +    this.writeNumberWithLength(number);
  1.9920 +
  1.9921 +    // Write unused octets 0xff, CCP and EXT1.
  1.9922 +    GsmPDUHelper.writeHexOctet(0xff);
  1.9923 +    GsmPDUHelper.writeHexOctet(0xff);
  1.9924 +    Buf.writeStringDelimiter(strLen);
  1.9925 +  },
  1.9926 +
  1.9927 +  /**
  1.9928 +   * Read Alpha Identifier.
  1.9929 +   *
  1.9930 +   * @see TS 131.102
  1.9931 +   *
  1.9932 +   * @param numOctets
  1.9933 +   *        Number of octets to be read.
  1.9934 +   *
  1.9935 +   * It uses either
  1.9936 +   *  1. SMS default 7-bit alphabet with bit 8 set to 0.
  1.9937 +   *  2. UCS2 string.
  1.9938 +   *
  1.9939 +   * Unused bytes should be set to 0xff.
  1.9940 +   */
  1.9941 +  readAlphaIdentifier: function(numOctets) {
  1.9942 +    if (numOctets === 0) {
  1.9943 +      return "";
  1.9944 +    }
  1.9945 +
  1.9946 +    let temp;
  1.9947 +    // Read the 1st octet to determine the encoding.
  1.9948 +    if ((temp = this.context.GsmPDUHelper.readHexOctet()) == 0x80 ||
  1.9949 +         temp == 0x81 ||
  1.9950 +         temp == 0x82) {
  1.9951 +      numOctets--;
  1.9952 +      return this.readICCUCS2String(temp, numOctets);
  1.9953 +    } else {
  1.9954 +      let Buf = this.context.Buf;
  1.9955 +      Buf.seekIncoming(-1 * Buf.PDU_HEX_OCTET_SIZE);
  1.9956 +      return this.read8BitUnpackedToString(numOctets);
  1.9957 +    }
  1.9958 +  },
  1.9959 +
  1.9960 +  /**
  1.9961 +   * Write Alpha Identifier.
  1.9962 +   *
  1.9963 +   * @param numOctets
  1.9964 +   *        Total number of octets to be written. This includes the length of
  1.9965 +   *        alphaId and the length of trailing unused octets(0xff).
  1.9966 +   * @param alphaId
  1.9967 +   *        Alpha Identifier to be written.
  1.9968 +   *
  1.9969 +   * Unused octets will be written as 0xff.
  1.9970 +   */
  1.9971 +  writeAlphaIdentifier: function(numOctets, alphaId) {
  1.9972 +    if (numOctets === 0) {
  1.9973 +      return;
  1.9974 +    }
  1.9975 +
  1.9976 +    // If alphaId is empty or it's of GSM 8 bit.
  1.9977 +    if (!alphaId || this.context.ICCUtilsHelper.isGsm8BitAlphabet(alphaId)) {
  1.9978 +      this.writeStringTo8BitUnpacked(numOctets, alphaId);
  1.9979 +    } else {
  1.9980 +      let GsmPDUHelper = this.context.GsmPDUHelper;
  1.9981 +
  1.9982 +      // Currently only support UCS2 coding scheme 0x80.
  1.9983 +      GsmPDUHelper.writeHexOctet(0x80);
  1.9984 +      numOctets--;
  1.9985 +      // Now the alphaId is UCS2 string, each character will take 2 octets.
  1.9986 +      if (alphaId.length * 2 > numOctets) {
  1.9987 +        alphaId = alphaId.substring(0, Math.floor(numOctets / 2));
  1.9988 +      }
  1.9989 +      GsmPDUHelper.writeUCS2String(alphaId);
  1.9990 +      for (let i = alphaId.length * 2; i < numOctets; i++) {
  1.9991 +        GsmPDUHelper.writeHexOctet(0xff);
  1.9992 +      }
  1.9993 +    }
  1.9994 +  },
  1.9995 +
  1.9996 +  /**
  1.9997 +   * Read Dialling number.
  1.9998 +   *
  1.9999 +   * @see TS 131.102
 1.10000 +   *
 1.10001 +   * @param len
 1.10002 +   *        The Length of BCD number.
 1.10003 +   *
 1.10004 +   * From TS 131.102, in EF_ADN, EF_FDN, the field 'Length of BCD number'
 1.10005 +   * means the total bytes should be allocated to store the TON/NPI and
 1.10006 +   * the dialing number.
 1.10007 +   * For example, if the dialing number is 1234567890,
 1.10008 +   * and the TON/NPI is 0x81,
 1.10009 +   * The field 'Length of BCD number' should be 06, which is
 1.10010 +   * 1 byte to store the TON/NPI, 0x81
 1.10011 +   * 5 bytes to store the BCD number 2143658709.
 1.10012 +   *
 1.10013 +   * Here the definition of the length is different from SMS spec,
 1.10014 +   * TS 23.040 9.1.2.5, which the length means
 1.10015 +   * "number of useful semi-octets within the Address-Value field".
 1.10016 +   */
 1.10017 +  readDiallingNumber: function(len) {
 1.10018 +    if (DEBUG) this.context.debug("PDU: Going to read Dialling number: " + len);
 1.10019 +    if (len === 0) {
 1.10020 +      return "";
 1.10021 +    }
 1.10022 +
 1.10023 +    let GsmPDUHelper = this.context.GsmPDUHelper;
 1.10024 +
 1.10025 +    // TOA = TON + NPI
 1.10026 +    let toa = GsmPDUHelper.readHexOctet();
 1.10027 +
 1.10028 +    let number = GsmPDUHelper.readSwappedNibbleBcdString(len - 1);
 1.10029 +    if (number.length <= 0) {
 1.10030 +      if (DEBUG) this.context.debug("No number provided");
 1.10031 +      return "";
 1.10032 +    }
 1.10033 +    if ((toa >> 4) == (PDU_TOA_INTERNATIONAL >> 4)) {
 1.10034 +      number = '+' + number;
 1.10035 +    }
 1.10036 +    return number;
 1.10037 +  },
 1.10038 +
 1.10039 +  /**
 1.10040 +   * Write Dialling Number.
 1.10041 +   *
 1.10042 +   * @param number  The Dialling number
 1.10043 +   */
 1.10044 +  writeDiallingNumber: function(number) {
 1.10045 +    let GsmPDUHelper = this.context.GsmPDUHelper;
 1.10046 +
 1.10047 +    let toa = PDU_TOA_ISDN; // 81
 1.10048 +    if (number[0] == '+') {
 1.10049 +      toa = PDU_TOA_INTERNATIONAL | PDU_TOA_ISDN; // 91
 1.10050 +      number = number.substring(1);
 1.10051 +    }
 1.10052 +    GsmPDUHelper.writeHexOctet(toa);
 1.10053 +    GsmPDUHelper.writeSwappedNibbleBCD(number);
 1.10054 +  },
 1.10055 +
 1.10056 +  readNumberWithLength: function() {
 1.10057 +    let Buf = this.context.Buf;
 1.10058 +    let number;
 1.10059 +    let numLen = this.context.GsmPDUHelper.readHexOctet();
 1.10060 +    if (numLen != 0xff) {
 1.10061 +      if (numLen > ADN_MAX_BCD_NUMBER_BYTES) {
 1.10062 +        throw new Error("invalid length of BCD number/SSC contents - " + numLen);
 1.10063 +      }
 1.10064 +
 1.10065 +      number = this.readDiallingNumber(numLen);
 1.10066 +      Buf.seekIncoming((ADN_MAX_BCD_NUMBER_BYTES - numLen) * Buf.PDU_HEX_OCTET_SIZE);
 1.10067 +    } else {
 1.10068 +      Buf.seekIncoming(ADN_MAX_BCD_NUMBER_BYTES * Buf.PDU_HEX_OCTET_SIZE);
 1.10069 +    }
 1.10070 +
 1.10071 +    return number;
 1.10072 +  },
 1.10073 +
 1.10074 +  writeNumberWithLength: function(number) {
 1.10075 +    let GsmPDUHelper = this.context.GsmPDUHelper;
 1.10076 +
 1.10077 +    if (number) {
 1.10078 +      let numStart = number[0] == "+" ? 1 : 0;
 1.10079 +      number = number.substring(0, numStart) +
 1.10080 +               number.substring(numStart)
 1.10081 +                     .replace(/[^0-9*#,]/g, "")
 1.10082 +                     .replace(/\*/g, "a")
 1.10083 +                     .replace(/\#/g, "b")
 1.10084 +                     .replace(/\,/g, "c");
 1.10085 +
 1.10086 +      let numDigits = number.length - numStart;
 1.10087 +      if (numDigits > ADN_MAX_NUMBER_DIGITS) {
 1.10088 +        number = number.substring(0, ADN_MAX_NUMBER_DIGITS + numStart);
 1.10089 +        numDigits = number.length - numStart;
 1.10090 +      }
 1.10091 +
 1.10092 +      // +1 for TON/NPI
 1.10093 +      let numLen = Math.ceil(numDigits / 2) + 1;
 1.10094 +      GsmPDUHelper.writeHexOctet(numLen);
 1.10095 +      this.writeDiallingNumber(number);
 1.10096 +      // Write trailing 0xff of Dialling Number.
 1.10097 +      for (let i = 0; i < ADN_MAX_BCD_NUMBER_BYTES - numLen; i++) {
 1.10098 +        GsmPDUHelper.writeHexOctet(0xff);
 1.10099 +      }
 1.10100 +    } else {
 1.10101 +      // +1 for numLen
 1.10102 +      for (let i = 0; i < ADN_MAX_BCD_NUMBER_BYTES + 1; i++) {
 1.10103 +        GsmPDUHelper.writeHexOctet(0xff);
 1.10104 +      }
 1.10105 +    }
 1.10106 +  }
 1.10107 +};
 1.10108 +
 1.10109 +function StkCommandParamsFactoryObject(aContext) {
 1.10110 +  this.context = aContext;
 1.10111 +}
 1.10112 +StkCommandParamsFactoryObject.prototype = {
 1.10113 +  context: null,
 1.10114 +
 1.10115 +  createParam: function(cmdDetails, ctlvs) {
 1.10116 +    let method = this[cmdDetails.typeOfCommand];
 1.10117 +    if (typeof method != "function") {
 1.10118 +      if (DEBUG) {
 1.10119 +        this.context.debug("Unknown proactive command " +
 1.10120 +                           cmdDetails.typeOfCommand.toString(16));
 1.10121 +      }
 1.10122 +      return null;
 1.10123 +    }
 1.10124 +    return method.call(this, cmdDetails, ctlvs);
 1.10125 +  },
 1.10126 +
 1.10127 +  /**
 1.10128 +   * Construct a param for Refresh.
 1.10129 +   *
 1.10130 +   * @param cmdDetails
 1.10131 +   *        The value object of CommandDetails TLV.
 1.10132 +   * @param ctlvs
 1.10133 +   *        The all TLVs in this proactive command.
 1.10134 +   */
 1.10135 +  processRefresh: function(cmdDetails, ctlvs) {
 1.10136 +    let refreshType = cmdDetails.commandQualifier;
 1.10137 +    switch (refreshType) {
 1.10138 +      case STK_REFRESH_FILE_CHANGE:
 1.10139 +      case STK_REFRESH_NAA_INIT_AND_FILE_CHANGE:
 1.10140 +        let ctlv = this.context.StkProactiveCmdHelper.searchForTag(
 1.10141 +          COMPREHENSIONTLV_TAG_FILE_LIST, ctlvs);
 1.10142 +        if (ctlv) {
 1.10143 +          let list = ctlv.value.fileList;
 1.10144 +          if (DEBUG) {
 1.10145 +            this.context.debug("Refresh, list = " + list);
 1.10146 +          }
 1.10147 +          this.context.ICCRecordHelper.fetchICCRecords();
 1.10148 +        }
 1.10149 +        break;
 1.10150 +    }
 1.10151 +    return null;
 1.10152 +  },
 1.10153 +
 1.10154 +  /**
 1.10155 +   * Construct a param for Poll Interval.
 1.10156 +   *
 1.10157 +   * @param cmdDetails
 1.10158 +   *        The value object of CommandDetails TLV.
 1.10159 +   * @param ctlvs
 1.10160 +   *        The all TLVs in this proactive command.
 1.10161 +   */
 1.10162 +  processPollInterval: function(cmdDetails, ctlvs) {
 1.10163 +    let ctlv = this.context.StkProactiveCmdHelper.searchForTag(
 1.10164 +        COMPREHENSIONTLV_TAG_DURATION, ctlvs);
 1.10165 +    if (!ctlv) {
 1.10166 +      this.context.RIL.sendStkTerminalResponse({
 1.10167 +        command: cmdDetails,
 1.10168 +        resultCode: STK_RESULT_REQUIRED_VALUES_MISSING});
 1.10169 +      throw new Error("Stk Poll Interval: Required value missing : Duration");
 1.10170 +    }
 1.10171 +
 1.10172 +    return ctlv.value;
 1.10173 +  },
 1.10174 +
 1.10175 +  /**
 1.10176 +   * Construct a param for Poll Off.
 1.10177 +   *
 1.10178 +   * @param cmdDetails
 1.10179 +   *        The value object of CommandDetails TLV.
 1.10180 +   * @param ctlvs
 1.10181 +   *        The all TLVs in this proactive command.
 1.10182 +   */
 1.10183 +  processPollOff: function(cmdDetails, ctlvs) {
 1.10184 +    return null;
 1.10185 +  },
 1.10186 +
 1.10187 +  /**
 1.10188 +   * Construct a param for Set Up Event list.
 1.10189 +   *
 1.10190 +   * @param cmdDetails
 1.10191 +   *        The value object of CommandDetails TLV.
 1.10192 +   * @param ctlvs
 1.10193 +   *        The all TLVs in this proactive command.
 1.10194 +   */
 1.10195 +  processSetUpEventList: function(cmdDetails, ctlvs) {
 1.10196 +    let ctlv = this.context.StkProactiveCmdHelper.searchForTag(
 1.10197 +        COMPREHENSIONTLV_TAG_EVENT_LIST, ctlvs);
 1.10198 +    if (!ctlv) {
 1.10199 +      this.context.RIL.sendStkTerminalResponse({
 1.10200 +        command: cmdDetails,
 1.10201 +        resultCode: STK_RESULT_REQUIRED_VALUES_MISSING});
 1.10202 +      throw new Error("Stk Event List: Required value missing : Event List");
 1.10203 +    }
 1.10204 +
 1.10205 +    return ctlv.value || {eventList: null};
 1.10206 +  },
 1.10207 +
 1.10208 +  /**
 1.10209 +   * Construct a param for Select Item.
 1.10210 +   *
 1.10211 +   * @param cmdDetails
 1.10212 +   *        The value object of CommandDetails TLV.
 1.10213 +   * @param ctlvs
 1.10214 +   *        The all TLVs in this proactive command.
 1.10215 +   */
 1.10216 +  processSelectItem: function(cmdDetails, ctlvs) {
 1.10217 +    let StkProactiveCmdHelper = this.context.StkProactiveCmdHelper;
 1.10218 +    let menu = {};
 1.10219 +
 1.10220 +    let ctlv = StkProactiveCmdHelper.searchForTag(COMPREHENSIONTLV_TAG_ALPHA_ID, ctlvs);
 1.10221 +    if (ctlv) {
 1.10222 +      menu.title = ctlv.value.identifier;
 1.10223 +    }
 1.10224 +
 1.10225 +    menu.items = [];
 1.10226 +    for (let i = 0; i < ctlvs.length; i++) {
 1.10227 +      let ctlv = ctlvs[i];
 1.10228 +      if (ctlv.tag == COMPREHENSIONTLV_TAG_ITEM) {
 1.10229 +        menu.items.push(ctlv.value);
 1.10230 +      }
 1.10231 +    }
 1.10232 +
 1.10233 +    if (menu.items.length === 0) {
 1.10234 +      this.context.RIL.sendStkTerminalResponse({
 1.10235 +        command: cmdDetails,
 1.10236 +        resultCode: STK_RESULT_REQUIRED_VALUES_MISSING});
 1.10237 +      throw new Error("Stk Menu: Required value missing : items");
 1.10238 +    }
 1.10239 +
 1.10240 +    ctlv = StkProactiveCmdHelper.searchForTag(COMPREHENSIONTLV_TAG_ITEM_ID, ctlvs);
 1.10241 +    if (ctlv) {
 1.10242 +      menu.defaultItem = ctlv.value.identifier - 1;
 1.10243 +    }
 1.10244 +
 1.10245 +    // The 1st bit and 2nd bit determines the presentation type.
 1.10246 +    menu.presentationType = cmdDetails.commandQualifier & 0x03;
 1.10247 +
 1.10248 +    // Help information available.
 1.10249 +    if (cmdDetails.commandQualifier & 0x80) {
 1.10250 +      menu.isHelpAvailable = true;
 1.10251 +    }
 1.10252 +
 1.10253 +    ctlv = StkProactiveCmdHelper.searchForTag(COMPREHENSIONTLV_TAG_NEXT_ACTION_IND, ctlvs);
 1.10254 +    if (ctlv) {
 1.10255 +      menu.nextActionList = ctlv.value;
 1.10256 +    }
 1.10257 +
 1.10258 +    return menu;
 1.10259 +  },
 1.10260 +
 1.10261 +  processDisplayText: function(cmdDetails, ctlvs) {
 1.10262 +    let StkProactiveCmdHelper = this.context.StkProactiveCmdHelper;
 1.10263 +    let textMsg = {};
 1.10264 +
 1.10265 +    let ctlv = StkProactiveCmdHelper.searchForTag(COMPREHENSIONTLV_TAG_TEXT_STRING, ctlvs);
 1.10266 +    if (!ctlv) {
 1.10267 +      this.context.RIL.sendStkTerminalResponse({
 1.10268 +        command: cmdDetails,
 1.10269 +        resultCode: STK_RESULT_REQUIRED_VALUES_MISSING});
 1.10270 +      throw new Error("Stk Display Text: Required value missing : Text String");
 1.10271 +    }
 1.10272 +    textMsg.text = ctlv.value.textString;
 1.10273 +
 1.10274 +    ctlv = StkProactiveCmdHelper.searchForTag(COMPREHENSIONTLV_TAG_IMMEDIATE_RESPONSE, ctlvs);
 1.10275 +    if (ctlv) {
 1.10276 +      textMsg.responseNeeded = true;
 1.10277 +    }
 1.10278 +
 1.10279 +    ctlv = StkProactiveCmdHelper.searchForTag(COMPREHENSIONTLV_TAG_DURATION, ctlvs);
 1.10280 +    if (ctlv) {
 1.10281 +      textMsg.duration = ctlv.value;
 1.10282 +    }
 1.10283 +
 1.10284 +    // High priority.
 1.10285 +    if (cmdDetails.commandQualifier & 0x01) {
 1.10286 +      textMsg.isHighPriority = true;
 1.10287 +    }
 1.10288 +
 1.10289 +    // User clear.
 1.10290 +    if (cmdDetails.commandQualifier & 0x80) {
 1.10291 +      textMsg.userClear = true;
 1.10292 +    }
 1.10293 +
 1.10294 +    return textMsg;
 1.10295 +  },
 1.10296 +
 1.10297 +  processSetUpIdleModeText: function(cmdDetails, ctlvs) {
 1.10298 +    let textMsg = {};
 1.10299 +
 1.10300 +    let ctlv = this.context.StkProactiveCmdHelper.searchForTag(
 1.10301 +        COMPREHENSIONTLV_TAG_TEXT_STRING, ctlvs);
 1.10302 +    if (!ctlv) {
 1.10303 +      this.context.RIL.sendStkTerminalResponse({
 1.10304 +        command: cmdDetails,
 1.10305 +        resultCode: STK_RESULT_REQUIRED_VALUES_MISSING});
 1.10306 +      throw new Error("Stk Set Up Idle Text: Required value missing : Text String");
 1.10307 +    }
 1.10308 +    textMsg.text = ctlv.value.textString;
 1.10309 +
 1.10310 +    return textMsg;
 1.10311 +  },
 1.10312 +
 1.10313 +  processGetInkey: function(cmdDetails, ctlvs) {
 1.10314 +    let StkProactiveCmdHelper = this.context.StkProactiveCmdHelper;
 1.10315 +    let input = {};
 1.10316 +
 1.10317 +    let ctlv = StkProactiveCmdHelper.searchForTag(COMPREHENSIONTLV_TAG_TEXT_STRING, ctlvs);
 1.10318 +    if (!ctlv) {
 1.10319 +      this.context.RIL.sendStkTerminalResponse({
 1.10320 +        command: cmdDetails,
 1.10321 +        resultCode: STK_RESULT_REQUIRED_VALUES_MISSING});
 1.10322 +      throw new Error("Stk Get InKey: Required value missing : Text String");
 1.10323 +    }
 1.10324 +    input.text = ctlv.value.textString;
 1.10325 +
 1.10326 +    // duration
 1.10327 +    ctlv = StkProactiveCmdHelper.searchForTag(
 1.10328 +        COMPREHENSIONTLV_TAG_DURATION, ctlvs);
 1.10329 +    if (ctlv) {
 1.10330 +      input.duration = ctlv.value;
 1.10331 +    }
 1.10332 +
 1.10333 +    input.minLength = 1;
 1.10334 +    input.maxLength = 1;
 1.10335 +
 1.10336 +    // isAlphabet
 1.10337 +    if (cmdDetails.commandQualifier & 0x01) {
 1.10338 +      input.isAlphabet = true;
 1.10339 +    }
 1.10340 +
 1.10341 +    // UCS2
 1.10342 +    if (cmdDetails.commandQualifier & 0x02) {
 1.10343 +      input.isUCS2 = true;
 1.10344 +    }
 1.10345 +
 1.10346 +    // Character sets defined in bit 1 and bit 2 are disable and
 1.10347 +    // the YES/NO reponse is required.
 1.10348 +    if (cmdDetails.commandQualifier & 0x04) {
 1.10349 +      input.isYesNoRequested = true;
 1.10350 +    }
 1.10351 +
 1.10352 +    // Help information available.
 1.10353 +    if (cmdDetails.commandQualifier & 0x80) {
 1.10354 +      input.isHelpAvailable = true;
 1.10355 +    }
 1.10356 +
 1.10357 +    return input;
 1.10358 +  },
 1.10359 +
 1.10360 +  processGetInput: function(cmdDetails, ctlvs) {
 1.10361 +    let StkProactiveCmdHelper = this.context.StkProactiveCmdHelper;
 1.10362 +    let input = {};
 1.10363 +
 1.10364 +    let ctlv = StkProactiveCmdHelper.searchForTag(COMPREHENSIONTLV_TAG_TEXT_STRING, ctlvs);
 1.10365 +    if (!ctlv) {
 1.10366 +      this.context.RIL.sendStkTerminalResponse({
 1.10367 +        command: cmdDetails,
 1.10368 +        resultCode: STK_RESULT_REQUIRED_VALUES_MISSING});
 1.10369 +      throw new Error("Stk Get Input: Required value missing : Text String");
 1.10370 +    }
 1.10371 +    input.text = ctlv.value.textString;
 1.10372 +
 1.10373 +    ctlv = StkProactiveCmdHelper.searchForTag(COMPREHENSIONTLV_TAG_RESPONSE_LENGTH, ctlvs);
 1.10374 +    if (ctlv) {
 1.10375 +      input.minLength = ctlv.value.minLength;
 1.10376 +      input.maxLength = ctlv.value.maxLength;
 1.10377 +    }
 1.10378 +
 1.10379 +    ctlv = StkProactiveCmdHelper.searchForTag(COMPREHENSIONTLV_TAG_DEFAULT_TEXT, ctlvs);
 1.10380 +    if (ctlv) {
 1.10381 +      input.defaultText = ctlv.value.textString;
 1.10382 +    }
 1.10383 +
 1.10384 +    // Alphabet only
 1.10385 +    if (cmdDetails.commandQualifier & 0x01) {
 1.10386 +      input.isAlphabet = true;
 1.10387 +    }
 1.10388 +
 1.10389 +    // UCS2
 1.10390 +    if (cmdDetails.commandQualifier & 0x02) {
 1.10391 +      input.isUCS2 = true;
 1.10392 +    }
 1.10393 +
 1.10394 +    // User input shall not be revealed
 1.10395 +    if (cmdDetails.commandQualifier & 0x04) {
 1.10396 +      input.hideInput = true;
 1.10397 +    }
 1.10398 +
 1.10399 +    // User input in SMS packed format
 1.10400 +    if (cmdDetails.commandQualifier & 0x08) {
 1.10401 +      input.isPacked = true;
 1.10402 +    }
 1.10403 +
 1.10404 +    // Help information available.
 1.10405 +    if (cmdDetails.commandQualifier & 0x80) {
 1.10406 +      input.isHelpAvailable = true;
 1.10407 +    }
 1.10408 +
 1.10409 +    return input;
 1.10410 +  },
 1.10411 +
 1.10412 +  processEventNotify: function(cmdDetails, ctlvs) {
 1.10413 +    let textMsg = {};
 1.10414 +
 1.10415 +    let ctlv = this.context.StkProactiveCmdHelper.searchForTag(
 1.10416 +        COMPREHENSIONTLV_TAG_ALPHA_ID, ctlvs);
 1.10417 +    if (!ctlv) {
 1.10418 +      this.context.RIL.sendStkTerminalResponse({
 1.10419 +        command: cmdDetails,
 1.10420 +        resultCode: STK_RESULT_REQUIRED_VALUES_MISSING});
 1.10421 +      throw new Error("Stk Event Notfiy: Required value missing : Alpha ID");
 1.10422 +    }
 1.10423 +    textMsg.text = ctlv.value.identifier;
 1.10424 +
 1.10425 +    return textMsg;
 1.10426 +  },
 1.10427 +
 1.10428 +  processSetupCall: function(cmdDetails, ctlvs) {
 1.10429 +    let StkProactiveCmdHelper = this.context.StkProactiveCmdHelper;
 1.10430 +    let call = {};
 1.10431 +    let iter = Iterator(ctlvs);
 1.10432 +
 1.10433 +    let ctlv = StkProactiveCmdHelper.searchForNextTag(COMPREHENSIONTLV_TAG_ALPHA_ID, iter);
 1.10434 +    if (ctlv) {
 1.10435 +      call.confirmMessage = ctlv.value.identifier;
 1.10436 +    }
 1.10437 +
 1.10438 +    ctlv = StkProactiveCmdHelper.searchForNextTag(COMPREHENSIONTLV_TAG_ALPHA_ID, iter);
 1.10439 +    if (ctlv) {
 1.10440 +      call.callMessage = ctlv.value.identifier;
 1.10441 +    }
 1.10442 +
 1.10443 +    ctlv = StkProactiveCmdHelper.searchForTag(COMPREHENSIONTLV_TAG_ADDRESS, ctlvs);
 1.10444 +    if (!ctlv) {
 1.10445 +      this.context.RIL.sendStkTerminalResponse({
 1.10446 +        command: cmdDetails,
 1.10447 +        resultCode: STK_RESULT_REQUIRED_VALUES_MISSING});
 1.10448 +      throw new Error("Stk Set Up Call: Required value missing : Adress");
 1.10449 +    }
 1.10450 +    call.address = ctlv.value.number;
 1.10451 +
 1.10452 +    // see 3GPP TS 31.111 section 6.4.13
 1.10453 +    ctlv = StkProactiveCmdHelper.searchForTag(COMPREHENSIONTLV_TAG_DURATION, ctlvs);
 1.10454 +    if (ctlv) {
 1.10455 +      call.duration = ctlv.value;
 1.10456 +    }
 1.10457 +
 1.10458 +    return call;
 1.10459 +  },
 1.10460 +
 1.10461 +  processLaunchBrowser: function(cmdDetails, ctlvs) {
 1.10462 +    let StkProactiveCmdHelper = this.context.StkProactiveCmdHelper;
 1.10463 +    let browser = {};
 1.10464 +
 1.10465 +    let ctlv = StkProactiveCmdHelper.searchForTag(COMPREHENSIONTLV_TAG_URL, ctlvs);
 1.10466 +    if (!ctlv) {
 1.10467 +      this.context.RIL.sendStkTerminalResponse({
 1.10468 +        command: cmdDetails,
 1.10469 +        resultCode: STK_RESULT_REQUIRED_VALUES_MISSING});
 1.10470 +      throw new Error("Stk Launch Browser: Required value missing : URL");
 1.10471 +    }
 1.10472 +    browser.url = ctlv.value.url;
 1.10473 +
 1.10474 +    ctlv = StkProactiveCmdHelper.searchForTag(COMPREHENSIONTLV_TAG_ALPHA_ID, ctlvs);
 1.10475 +    if (ctlv) {
 1.10476 +      browser.confirmMessage = ctlv.value.identifier;
 1.10477 +    }
 1.10478 +
 1.10479 +    browser.mode = cmdDetails.commandQualifier & 0x03;
 1.10480 +
 1.10481 +    return browser;
 1.10482 +  },
 1.10483 +
 1.10484 +  processPlayTone: function(cmdDetails, ctlvs) {
 1.10485 +    let StkProactiveCmdHelper = this.context.StkProactiveCmdHelper;
 1.10486 +    let playTone = {};
 1.10487 +
 1.10488 +    let ctlv = StkProactiveCmdHelper.searchForTag(
 1.10489 +        COMPREHENSIONTLV_TAG_ALPHA_ID, ctlvs);
 1.10490 +    if (ctlv) {
 1.10491 +      playTone.text = ctlv.value.identifier;
 1.10492 +    }
 1.10493 +
 1.10494 +    ctlv = StkProactiveCmdHelper.searchForTag(COMPREHENSIONTLV_TAG_TONE, ctlvs);
 1.10495 +    if (ctlv) {
 1.10496 +      playTone.tone = ctlv.value.tone;
 1.10497 +    }
 1.10498 +
 1.10499 +    ctlv = StkProactiveCmdHelper.searchForTag(
 1.10500 +        COMPREHENSIONTLV_TAG_DURATION, ctlvs);
 1.10501 +    if (ctlv) {
 1.10502 +      playTone.duration = ctlv.value;
 1.10503 +    }
 1.10504 +
 1.10505 +    // vibrate is only defined in TS 102.223
 1.10506 +    playTone.isVibrate = (cmdDetails.commandQualifier & 0x01) !== 0x00;
 1.10507 +
 1.10508 +    return playTone;
 1.10509 +  },
 1.10510 +
 1.10511 +  /**
 1.10512 +   * Construct a param for Provide Local Information
 1.10513 +   *
 1.10514 +   * @param cmdDetails
 1.10515 +   *        The value object of CommandDetails TLV.
 1.10516 +   * @param ctlvs
 1.10517 +   *        The all TLVs in this proactive command.
 1.10518 +   */
 1.10519 +  processProvideLocalInfo: function(cmdDetails, ctlvs) {
 1.10520 +    let provideLocalInfo = {
 1.10521 +      localInfoType: cmdDetails.commandQualifier
 1.10522 +    };
 1.10523 +    return provideLocalInfo;
 1.10524 +  },
 1.10525 +
 1.10526 +  processTimerManagement: function(cmdDetails, ctlvs) {
 1.10527 +    let StkProactiveCmdHelper = this.context.StkProactiveCmdHelper;
 1.10528 +    let timer = {
 1.10529 +      timerAction: cmdDetails.commandQualifier
 1.10530 +    };
 1.10531 +
 1.10532 +    let ctlv = StkProactiveCmdHelper.searchForTag(
 1.10533 +        COMPREHENSIONTLV_TAG_TIMER_IDENTIFIER, ctlvs);
 1.10534 +    if (ctlv) {
 1.10535 +      timer.timerId = ctlv.value.timerId;
 1.10536 +    }
 1.10537 +
 1.10538 +    ctlv = StkProactiveCmdHelper.searchForTag(
 1.10539 +        COMPREHENSIONTLV_TAG_TIMER_VALUE, ctlvs);
 1.10540 +    if (ctlv) {
 1.10541 +      timer.timerValue = ctlv.value.timerValue;
 1.10542 +    }
 1.10543 +
 1.10544 +    return timer;
 1.10545 +  },
 1.10546 +
 1.10547 +   /**
 1.10548 +    * Construct a param for BIP commands.
 1.10549 +    *
 1.10550 +    * @param cmdDetails
 1.10551 +    *        The value object of CommandDetails TLV.
 1.10552 +    * @param ctlvs
 1.10553 +    *        The all TLVs in this proactive command.
 1.10554 +    */
 1.10555 +  processBipMessage: function(cmdDetails, ctlvs) {
 1.10556 +    let bipMsg = {};
 1.10557 +
 1.10558 +    let ctlv = this.context.StkProactiveCmdHelper.searchForTag(
 1.10559 +        COMPREHENSIONTLV_TAG_ALPHA_ID, ctlvs);
 1.10560 +    if (ctlv) {
 1.10561 +      bipMsg.text = ctlv.value.identifier;
 1.10562 +    }
 1.10563 +
 1.10564 +    return bipMsg;
 1.10565 +  }
 1.10566 +};
 1.10567 +StkCommandParamsFactoryObject.prototype[STK_CMD_REFRESH] = function STK_CMD_REFRESH(cmdDetails, ctlvs) {
 1.10568 +  return this.processRefresh(cmdDetails, ctlvs);
 1.10569 +};
 1.10570 +StkCommandParamsFactoryObject.prototype[STK_CMD_POLL_INTERVAL] = function STK_CMD_POLL_INTERVAL(cmdDetails, ctlvs) {
 1.10571 +  return this.processPollInterval(cmdDetails, ctlvs);
 1.10572 +};
 1.10573 +StkCommandParamsFactoryObject.prototype[STK_CMD_POLL_OFF] = function STK_CMD_POLL_OFF(cmdDetails, ctlvs) {
 1.10574 +  return this.processPollOff(cmdDetails, ctlvs);
 1.10575 +};
 1.10576 +StkCommandParamsFactoryObject.prototype[STK_CMD_PROVIDE_LOCAL_INFO] = function STK_CMD_PROVIDE_LOCAL_INFO(cmdDetails, ctlvs) {
 1.10577 +  return this.processProvideLocalInfo(cmdDetails, ctlvs);
 1.10578 +};
 1.10579 +StkCommandParamsFactoryObject.prototype[STK_CMD_SET_UP_EVENT_LIST] = function STK_CMD_SET_UP_EVENT_LIST(cmdDetails, ctlvs) {
 1.10580 +  return this.processSetUpEventList(cmdDetails, ctlvs);
 1.10581 +};
 1.10582 +StkCommandParamsFactoryObject.prototype[STK_CMD_SET_UP_MENU] = function STK_CMD_SET_UP_MENU(cmdDetails, ctlvs) {
 1.10583 +  return this.processSelectItem(cmdDetails, ctlvs);
 1.10584 +};
 1.10585 +StkCommandParamsFactoryObject.prototype[STK_CMD_SELECT_ITEM] = function STK_CMD_SELECT_ITEM(cmdDetails, ctlvs) {
 1.10586 +  return this.processSelectItem(cmdDetails, ctlvs);
 1.10587 +};
 1.10588 +StkCommandParamsFactoryObject.prototype[STK_CMD_DISPLAY_TEXT] = function STK_CMD_DISPLAY_TEXT(cmdDetails, ctlvs) {
 1.10589 +  return this.processDisplayText(cmdDetails, ctlvs);
 1.10590 +};
 1.10591 +StkCommandParamsFactoryObject.prototype[STK_CMD_SET_UP_IDLE_MODE_TEXT] = function STK_CMD_SET_UP_IDLE_MODE_TEXT(cmdDetails, ctlvs) {
 1.10592 +  return this.processSetUpIdleModeText(cmdDetails, ctlvs);
 1.10593 +};
 1.10594 +StkCommandParamsFactoryObject.prototype[STK_CMD_GET_INKEY] = function STK_CMD_GET_INKEY(cmdDetails, ctlvs) {
 1.10595 +  return this.processGetInkey(cmdDetails, ctlvs);
 1.10596 +};
 1.10597 +StkCommandParamsFactoryObject.prototype[STK_CMD_GET_INPUT] = function STK_CMD_GET_INPUT(cmdDetails, ctlvs) {
 1.10598 +  return this.processGetInput(cmdDetails, ctlvs);
 1.10599 +};
 1.10600 +StkCommandParamsFactoryObject.prototype[STK_CMD_SEND_SS] = function STK_CMD_SEND_SS(cmdDetails, ctlvs) {
 1.10601 +  return this.processEventNotify(cmdDetails, ctlvs);
 1.10602 +};
 1.10603 +StkCommandParamsFactoryObject.prototype[STK_CMD_SEND_USSD] = function STK_CMD_SEND_USSD(cmdDetails, ctlvs) {
 1.10604 +  return this.processEventNotify(cmdDetails, ctlvs);
 1.10605 +};
 1.10606 +StkCommandParamsFactoryObject.prototype[STK_CMD_SEND_SMS] = function STK_CMD_SEND_SMS(cmdDetails, ctlvs) {
 1.10607 +  return this.processEventNotify(cmdDetails, ctlvs);
 1.10608 +};
 1.10609 +StkCommandParamsFactoryObject.prototype[STK_CMD_SEND_DTMF] = function STK_CMD_SEND_DTMF(cmdDetails, ctlvs) {
 1.10610 +  return this.processEventNotify(cmdDetails, ctlvs);
 1.10611 +};
 1.10612 +StkCommandParamsFactoryObject.prototype[STK_CMD_SET_UP_CALL] = function STK_CMD_SET_UP_CALL(cmdDetails, ctlvs) {
 1.10613 +  return this.processSetupCall(cmdDetails, ctlvs);
 1.10614 +};
 1.10615 +StkCommandParamsFactoryObject.prototype[STK_CMD_LAUNCH_BROWSER] = function STK_CMD_LAUNCH_BROWSER(cmdDetails, ctlvs) {
 1.10616 +  return this.processLaunchBrowser(cmdDetails, ctlvs);
 1.10617 +};
 1.10618 +StkCommandParamsFactoryObject.prototype[STK_CMD_PLAY_TONE] = function STK_CMD_PLAY_TONE(cmdDetails, ctlvs) {
 1.10619 +  return this.processPlayTone(cmdDetails, ctlvs);
 1.10620 +};
 1.10621 +StkCommandParamsFactoryObject.prototype[STK_CMD_TIMER_MANAGEMENT] = function STK_CMD_TIMER_MANAGEMENT(cmdDetails, ctlvs) {
 1.10622 +  return this.processTimerManagement(cmdDetails, ctlvs);
 1.10623 +};
 1.10624 +StkCommandParamsFactoryObject.prototype[STK_CMD_OPEN_CHANNEL] = function STK_CMD_OPEN_CHANNEL(cmdDetails, ctlvs) {
 1.10625 +  return this.processBipMessage(cmdDetails, ctlvs);
 1.10626 +};
 1.10627 +StkCommandParamsFactoryObject.prototype[STK_CMD_CLOSE_CHANNEL] = function STK_CMD_CLOSE_CHANNEL(cmdDetails, ctlvs) {
 1.10628 +  return this.processBipMessage(cmdDetails, ctlvs);
 1.10629 +};
 1.10630 +StkCommandParamsFactoryObject.prototype[STK_CMD_RECEIVE_DATA] = function STK_CMD_RECEIVE_DATA(cmdDetails, ctlvs) {
 1.10631 +  return this.processBipMessage(cmdDetails, ctlvs);
 1.10632 +};
 1.10633 +StkCommandParamsFactoryObject.prototype[STK_CMD_SEND_DATA] = function STK_CMD_SEND_DATA(cmdDetails, ctlvs) {
 1.10634 +  return this.processBipMessage(cmdDetails, ctlvs);
 1.10635 +};
 1.10636 +
 1.10637 +function StkProactiveCmdHelperObject(aContext) {
 1.10638 +  this.context = aContext;
 1.10639 +}
 1.10640 +StkProactiveCmdHelperObject.prototype = {
 1.10641 +  context: null,
 1.10642 +
 1.10643 +  retrieve: function(tag, length) {
 1.10644 +    let method = this[tag];
 1.10645 +    if (typeof method != "function") {
 1.10646 +      if (DEBUG) {
 1.10647 +        this.context.debug("Unknown comprehension tag " + tag.toString(16));
 1.10648 +      }
 1.10649 +      let Buf = this.context.Buf;
 1.10650 +      Buf.seekIncoming(length * Buf.PDU_HEX_OCTET_SIZE);
 1.10651 +      return null;
 1.10652 +    }
 1.10653 +    return method.call(this, length);
 1.10654 +  },
 1.10655 +
 1.10656 +  /**
 1.10657 +   * Command Details.
 1.10658 +   *
 1.10659 +   * | Byte | Description         | Length |
 1.10660 +   * |  1   | Command details Tag |   1    |
 1.10661 +   * |  2   | Length = 03         |   1    |
 1.10662 +   * |  3   | Command number      |   1    |
 1.10663 +   * |  4   | Type of Command     |   1    |
 1.10664 +   * |  5   | Command Qualifier   |   1    |
 1.10665 +   */
 1.10666 +  retrieveCommandDetails: function(length) {
 1.10667 +    let GsmPDUHelper = this.context.GsmPDUHelper;
 1.10668 +    let cmdDetails = {
 1.10669 +      commandNumber: GsmPDUHelper.readHexOctet(),
 1.10670 +      typeOfCommand: GsmPDUHelper.readHexOctet(),
 1.10671 +      commandQualifier: GsmPDUHelper.readHexOctet()
 1.10672 +    };
 1.10673 +    return cmdDetails;
 1.10674 +  },
 1.10675 +
 1.10676 +  /**
 1.10677 +   * Device Identities.
 1.10678 +   *
 1.10679 +   * | Byte | Description            | Length |
 1.10680 +   * |  1   | Device Identity Tag    |   1    |
 1.10681 +   * |  2   | Length = 02            |   1    |
 1.10682 +   * |  3   | Source device Identity |   1    |
 1.10683 +   * |  4   | Destination device Id  |   1    |
 1.10684 +   */
 1.10685 +  retrieveDeviceId: function(length) {
 1.10686 +    let GsmPDUHelper = this.context.GsmPDUHelper;
 1.10687 +    let deviceId = {
 1.10688 +      sourceId: GsmPDUHelper.readHexOctet(),
 1.10689 +      destinationId: GsmPDUHelper.readHexOctet()
 1.10690 +    };
 1.10691 +    return deviceId;
 1.10692 +  },
 1.10693 +
 1.10694 +  /**
 1.10695 +   * Alpha Identifier.
 1.10696 +   *
 1.10697 +   * | Byte         | Description            | Length |
 1.10698 +   * |  1           | Alpha Identifier Tag   |   1    |
 1.10699 +   * | 2 ~ (Y-1)+2  | Length (X)             |   Y    |
 1.10700 +   * | (Y-1)+3 ~    | Alpha identfier        |   X    |
 1.10701 +   * | (Y-1)+X+2    |                        |        |
 1.10702 +   */
 1.10703 +  retrieveAlphaId: function(length) {
 1.10704 +    let alphaId = {
 1.10705 +      identifier: this.context.ICCPDUHelper.readAlphaIdentifier(length)
 1.10706 +    };
 1.10707 +    return alphaId;
 1.10708 +  },
 1.10709 +
 1.10710 +  /**
 1.10711 +   * Duration.
 1.10712 +   *
 1.10713 +   * | Byte | Description           | Length |
 1.10714 +   * |  1   | Response Length Tag   |   1    |
 1.10715 +   * |  2   | Lenth = 02            |   1    |
 1.10716 +   * |  3   | Time unit             |   1    |
 1.10717 +   * |  4   | Time interval         |   1    |
 1.10718 +   */
 1.10719 +  retrieveDuration: function(length) {
 1.10720 +    let GsmPDUHelper = this.context.GsmPDUHelper;
 1.10721 +    let duration = {
 1.10722 +      timeUnit: GsmPDUHelper.readHexOctet(),
 1.10723 +      timeInterval: GsmPDUHelper.readHexOctet(),
 1.10724 +    };
 1.10725 +    return duration;
 1.10726 +  },
 1.10727 +
 1.10728 +  /**
 1.10729 +   * Address.
 1.10730 +   *
 1.10731 +   * | Byte         | Description            | Length |
 1.10732 +   * |  1           | Alpha Identifier Tag   |   1    |
 1.10733 +   * | 2 ~ (Y-1)+2  | Length (X)             |   Y    |
 1.10734 +   * | (Y-1)+3      | TON and NPI            |   1    |
 1.10735 +   * | (Y-1)+4 ~    | Dialling number        |   X    |
 1.10736 +   * | (Y-1)+X+2    |                        |        |
 1.10737 +   */
 1.10738 +  retrieveAddress: function(length) {
 1.10739 +    let address = {
 1.10740 +      number : this.context.ICCPDUHelper.readDiallingNumber(length)
 1.10741 +    };
 1.10742 +    return address;
 1.10743 +  },
 1.10744 +
 1.10745 +  /**
 1.10746 +   * Text String.
 1.10747 +   *
 1.10748 +   * | Byte         | Description        | Length |
 1.10749 +   * |  1           | Text String Tag    |   1    |
 1.10750 +   * | 2 ~ (Y-1)+2  | Length (X)         |   Y    |
 1.10751 +   * | (Y-1)+3      | Data coding scheme |   1    |
 1.10752 +   * | (Y-1)+4~     | Text String        |   X    |
 1.10753 +   * | (Y-1)+X+2    |                    |        |
 1.10754 +   */
 1.10755 +  retrieveTextString: function(length) {
 1.10756 +    if (!length) {
 1.10757 +      // null string.
 1.10758 +      return {textString: null};
 1.10759 +    }
 1.10760 +
 1.10761 +    let GsmPDUHelper = this.context.GsmPDUHelper;
 1.10762 +    let text = {
 1.10763 +      codingScheme: GsmPDUHelper.readHexOctet()
 1.10764 +    };
 1.10765 +
 1.10766 +    length--; // -1 for the codingScheme.
 1.10767 +    switch (text.codingScheme & 0x0f) {
 1.10768 +      case STK_TEXT_CODING_GSM_7BIT_PACKED:
 1.10769 +        text.textString = GsmPDUHelper.readSeptetsToString(length * 8 / 7, 0, 0, 0);
 1.10770 +        break;
 1.10771 +      case STK_TEXT_CODING_GSM_8BIT:
 1.10772 +        text.textString =
 1.10773 +          this.context.ICCPDUHelper.read8BitUnpackedToString(length);
 1.10774 +        break;
 1.10775 +      case STK_TEXT_CODING_UCS2:
 1.10776 +        text.textString = GsmPDUHelper.readUCS2String(length);
 1.10777 +        break;
 1.10778 +    }
 1.10779 +    return text;
 1.10780 +  },
 1.10781 +
 1.10782 +  /**
 1.10783 +   * Tone.
 1.10784 +   *
 1.10785 +   * | Byte | Description     | Length |
 1.10786 +   * |  1   | Tone Tag        |   1    |
 1.10787 +   * |  2   | Lenth = 01      |   1    |
 1.10788 +   * |  3   | Tone            |   1    |
 1.10789 +   */
 1.10790 +  retrieveTone: function(length) {
 1.10791 +    let tone = {
 1.10792 +      tone: this.context.GsmPDUHelper.readHexOctet(),
 1.10793 +    };
 1.10794 +    return tone;
 1.10795 +  },
 1.10796 +
 1.10797 +  /**
 1.10798 +   * Item.
 1.10799 +   *
 1.10800 +   * | Byte         | Description            | Length |
 1.10801 +   * |  1           | Item Tag               |   1    |
 1.10802 +   * | 2 ~ (Y-1)+2  | Length (X)             |   Y    |
 1.10803 +   * | (Y-1)+3      | Identifier of item     |   1    |
 1.10804 +   * | (Y-1)+4 ~    | Text string of item    |   X    |
 1.10805 +   * | (Y-1)+X+2    |                        |        |
 1.10806 +   */
 1.10807 +  retrieveItem: function(length) {
 1.10808 +    // TS 102.223 ,clause 6.6.7 SET-UP MENU
 1.10809 +    // If the "Item data object for item 1" is a null data object
 1.10810 +    // (i.e. length = '00' and no value part), this is an indication to the ME
 1.10811 +    // to remove the existing menu from the menu system in the ME.
 1.10812 +    if (!length) {
 1.10813 +      return null;
 1.10814 +    }
 1.10815 +    let item = {
 1.10816 +      identifier: this.context.GsmPDUHelper.readHexOctet(),
 1.10817 +      text: this.context.ICCPDUHelper.readAlphaIdentifier(length - 1)
 1.10818 +    };
 1.10819 +    return item;
 1.10820 +  },
 1.10821 +
 1.10822 +  /**
 1.10823 +   * Item Identifier.
 1.10824 +   *
 1.10825 +   * | Byte | Description               | Length |
 1.10826 +   * |  1   | Item Identifier Tag       |   1    |
 1.10827 +   * |  2   | Lenth = 01                |   1    |
 1.10828 +   * |  3   | Identifier of Item chosen |   1    |
 1.10829 +   */
 1.10830 +  retrieveItemId: function(length) {
 1.10831 +    let itemId = {
 1.10832 +      identifier: this.context.GsmPDUHelper.readHexOctet()
 1.10833 +    };
 1.10834 +    return itemId;
 1.10835 +  },
 1.10836 +
 1.10837 +  /**
 1.10838 +   * Response Length.
 1.10839 +   *
 1.10840 +   * | Byte | Description                | Length |
 1.10841 +   * |  1   | Response Length Tag        |   1    |
 1.10842 +   * |  2   | Lenth = 02                 |   1    |
 1.10843 +   * |  3   | Minimum length of response |   1    |
 1.10844 +   * |  4   | Maximum length of response |   1    |
 1.10845 +   */
 1.10846 +  retrieveResponseLength: function(length) {
 1.10847 +    let GsmPDUHelper = this.context.GsmPDUHelper;
 1.10848 +    let rspLength = {
 1.10849 +      minLength : GsmPDUHelper.readHexOctet(),
 1.10850 +      maxLength : GsmPDUHelper.readHexOctet()
 1.10851 +    };
 1.10852 +    return rspLength;
 1.10853 +  },
 1.10854 +
 1.10855 +  /**
 1.10856 +   * File List.
 1.10857 +   *
 1.10858 +   * | Byte         | Description            | Length |
 1.10859 +   * |  1           | File List Tag          |   1    |
 1.10860 +   * | 2 ~ (Y-1)+2  | Length (X)             |   Y    |
 1.10861 +   * | (Y-1)+3      | Number of files        |   1    |
 1.10862 +   * | (Y-1)+4 ~    | Files                  |   X    |
 1.10863 +   * | (Y-1)+X+2    |                        |        |
 1.10864 +   */
 1.10865 +  retrieveFileList: function(length) {
 1.10866 +    let num = this.context.GsmPDUHelper.readHexOctet();
 1.10867 +    let fileList = "";
 1.10868 +    length--; // -1 for the num octet.
 1.10869 +    for (let i = 0; i < 2 * length; i++) {
 1.10870 +      // Didn't use readHexOctet here,
 1.10871 +      // otherwise 0x00 will be "0", not "00"
 1.10872 +      fileList += String.fromCharCode(this.context.Buf.readUint16());
 1.10873 +    }
 1.10874 +    return {
 1.10875 +      fileList: fileList
 1.10876 +    };
 1.10877 +  },
 1.10878 +
 1.10879 +  /**
 1.10880 +   * Default Text.
 1.10881 +   *
 1.10882 +   * Same as Text String.
 1.10883 +   */
 1.10884 +  retrieveDefaultText: function(length) {
 1.10885 +    return this.retrieveTextString(length);
 1.10886 +  },
 1.10887 +
 1.10888 +  /**
 1.10889 +   * Event List.
 1.10890 +   */
 1.10891 +  retrieveEventList: function(length) {
 1.10892 +    if (!length) {
 1.10893 +      // null means an indication to ME to remove the existing list of events
 1.10894 +      // in ME.
 1.10895 +      return null;
 1.10896 +    }
 1.10897 +
 1.10898 +    let GsmPDUHelper = this.context.GsmPDUHelper;
 1.10899 +    let eventList = [];
 1.10900 +    for (let i = 0; i < length; i++) {
 1.10901 +      eventList.push(GsmPDUHelper.readHexOctet());
 1.10902 +    }
 1.10903 +    return {
 1.10904 +      eventList: eventList
 1.10905 +    };
 1.10906 +  },
 1.10907 +
 1.10908 +  /**
 1.10909 +   * Timer Identifier.
 1.10910 +   *
 1.10911 +   * | Byte  | Description          | Length |
 1.10912 +   * |  1    | Timer Identifier Tag |   1    |
 1.10913 +   * |  2    | Length = 01          |   1    |
 1.10914 +   * |  3    | Timer Identifier     |   1    |
 1.10915 +   */
 1.10916 +  retrieveTimerId: function(length) {
 1.10917 +    let id = {
 1.10918 +      timerId: this.context.GsmPDUHelper.readHexOctet()
 1.10919 +    };
 1.10920 +    return id;
 1.10921 +  },
 1.10922 +
 1.10923 +  /**
 1.10924 +   * Timer Value.
 1.10925 +   *
 1.10926 +   * | Byte  | Description          | Length |
 1.10927 +   * |  1    | Timer Value Tag      |   1    |
 1.10928 +   * |  2    | Length = 03          |   1    |
 1.10929 +   * |  3    | Hour                 |   1    |
 1.10930 +   * |  4    | Minute               |   1    |
 1.10931 +   * |  5    | Second               |   1    |
 1.10932 +   */
 1.10933 +  retrieveTimerValue: function(length) {
 1.10934 +    let GsmPDUHelper = this.context.GsmPDUHelper;
 1.10935 +    let value = {
 1.10936 +      timerValue: (GsmPDUHelper.readSwappedNibbleBcdNum(1) * 60 * 60) +
 1.10937 +                  (GsmPDUHelper.readSwappedNibbleBcdNum(1) * 60) +
 1.10938 +                  (GsmPDUHelper.readSwappedNibbleBcdNum(1))
 1.10939 +    };
 1.10940 +    return value;
 1.10941 +  },
 1.10942 +
 1.10943 +  /**
 1.10944 +   * Immediate Response.
 1.10945 +   *
 1.10946 +   * | Byte  | Description            | Length |
 1.10947 +   * |  1    | Immediate Response Tag |   1    |
 1.10948 +   * |  2    | Length = 00            |   1    |
 1.10949 +   */
 1.10950 +  retrieveImmediaResponse: function(length) {
 1.10951 +    return {};
 1.10952 +  },
 1.10953 +
 1.10954 +  /**
 1.10955 +   * URL
 1.10956 +   *
 1.10957 +   * | Byte      | Description         | Length |
 1.10958 +   * |  1        | URL Tag             |   1    |
 1.10959 +   * | 2 ~ (Y+1) | Length(X)           |   Y    |
 1.10960 +   * | (Y+2) ~   | URL                 |   X    |
 1.10961 +   * | (Y+1+X)   |                     |        |
 1.10962 +   */
 1.10963 +  retrieveUrl: function(length) {
 1.10964 +    let GsmPDUHelper = this.context.GsmPDUHelper;
 1.10965 +    let s = "";
 1.10966 +    for (let i = 0; i < length; i++) {
 1.10967 +      s += String.fromCharCode(GsmPDUHelper.readHexOctet());
 1.10968 +    }
 1.10969 +    return {url: s};
 1.10970 +  },
 1.10971 +
 1.10972 +  /**
 1.10973 +   * Next Action Indicator List.
 1.10974 +   *
 1.10975 +   * | Byte  | Description      | Length |
 1.10976 +   * |  1    | Next Action tag  |   1    |
 1.10977 +   * |  1    | Length(X)        |   1    |
 1.10978 +   * |  3~   | Next Action List |   X    |
 1.10979 +   * | 3+X-1 |                  |        |
 1.10980 +   */
 1.10981 +  retrieveNextActionList: function(length) {
 1.10982 +    let GsmPDUHelper = this.context.GsmPDUHelper;
 1.10983 +    let nextActionList = [];
 1.10984 +    for (let i = 0; i < length; i++) {
 1.10985 +      nextActionList.push(GsmPDUHelper.readHexOctet());
 1.10986 +    }
 1.10987 +    return nextActionList;
 1.10988 +  },
 1.10989 +
 1.10990 +  searchForTag: function(tag, ctlvs) {
 1.10991 +    let iter = Iterator(ctlvs);
 1.10992 +    return this.searchForNextTag(tag, iter);
 1.10993 +  },
 1.10994 +
 1.10995 +  searchForNextTag: function(tag, iter) {
 1.10996 +    for (let [index, ctlv] in iter) {
 1.10997 +      if ((ctlv.tag & ~COMPREHENSIONTLV_FLAG_CR) == tag) {
 1.10998 +        return ctlv;
 1.10999 +      }
 1.11000 +    }
 1.11001 +    return null;
 1.11002 +  },
 1.11003 +};
 1.11004 +StkProactiveCmdHelperObject.prototype[COMPREHENSIONTLV_TAG_COMMAND_DETAILS] = function COMPREHENSIONTLV_TAG_COMMAND_DETAILS(length) {
 1.11005 +  return this.retrieveCommandDetails(length);
 1.11006 +};
 1.11007 +StkProactiveCmdHelperObject.prototype[COMPREHENSIONTLV_TAG_DEVICE_ID] = function COMPREHENSIONTLV_TAG_DEVICE_ID(length) {
 1.11008 +  return this.retrieveDeviceId(length);
 1.11009 +};
 1.11010 +StkProactiveCmdHelperObject.prototype[COMPREHENSIONTLV_TAG_ALPHA_ID] = function COMPREHENSIONTLV_TAG_ALPHA_ID(length) {
 1.11011 +  return this.retrieveAlphaId(length);
 1.11012 +};
 1.11013 +StkProactiveCmdHelperObject.prototype[COMPREHENSIONTLV_TAG_DURATION] = function COMPREHENSIONTLV_TAG_DURATION(length) {
 1.11014 +  return this.retrieveDuration(length);
 1.11015 +};
 1.11016 +StkProactiveCmdHelperObject.prototype[COMPREHENSIONTLV_TAG_ADDRESS] = function COMPREHENSIONTLV_TAG_ADDRESS(length) {
 1.11017 +  return this.retrieveAddress(length);
 1.11018 +};
 1.11019 +StkProactiveCmdHelperObject.prototype[COMPREHENSIONTLV_TAG_TEXT_STRING] = function COMPREHENSIONTLV_TAG_TEXT_STRING(length) {
 1.11020 +  return this.retrieveTextString(length);
 1.11021 +};
 1.11022 +StkProactiveCmdHelperObject.prototype[COMPREHENSIONTLV_TAG_TONE] = function COMPREHENSIONTLV_TAG_TONE(length) {
 1.11023 +  return this.retrieveTone(length);
 1.11024 +};
 1.11025 +StkProactiveCmdHelperObject.prototype[COMPREHENSIONTLV_TAG_ITEM] = function COMPREHENSIONTLV_TAG_ITEM(length) {
 1.11026 +  return this.retrieveItem(length);
 1.11027 +};
 1.11028 +StkProactiveCmdHelperObject.prototype[COMPREHENSIONTLV_TAG_ITEM_ID] = function COMPREHENSIONTLV_TAG_ITEM_ID(length) {
 1.11029 +  return this.retrieveItemId(length);
 1.11030 +};
 1.11031 +StkProactiveCmdHelperObject.prototype[COMPREHENSIONTLV_TAG_RESPONSE_LENGTH] = function COMPREHENSIONTLV_TAG_RESPONSE_LENGTH(length) {
 1.11032 +  return this.retrieveResponseLength(length);
 1.11033 +};
 1.11034 +StkProactiveCmdHelperObject.prototype[COMPREHENSIONTLV_TAG_FILE_LIST] = function COMPREHENSIONTLV_TAG_FILE_LIST(length) {
 1.11035 +  return this.retrieveFileList(length);
 1.11036 +};
 1.11037 +StkProactiveCmdHelperObject.prototype[COMPREHENSIONTLV_TAG_DEFAULT_TEXT] = function COMPREHENSIONTLV_TAG_DEFAULT_TEXT(length) {
 1.11038 +  return this.retrieveDefaultText(length);
 1.11039 +};
 1.11040 +StkProactiveCmdHelperObject.prototype[COMPREHENSIONTLV_TAG_EVENT_LIST] = function COMPREHENSIONTLV_TAG_EVENT_LIST(length) {
 1.11041 +  return this.retrieveEventList(length);
 1.11042 +};
 1.11043 +StkProactiveCmdHelperObject.prototype[COMPREHENSIONTLV_TAG_TIMER_IDENTIFIER] = function COMPREHENSIONTLV_TAG_TIMER_IDENTIFIER(length) {
 1.11044 +  return this.retrieveTimerId(length);
 1.11045 +};
 1.11046 +StkProactiveCmdHelperObject.prototype[COMPREHENSIONTLV_TAG_TIMER_VALUE] = function COMPREHENSIONTLV_TAG_TIMER_VALUE(length) {
 1.11047 +  return this.retrieveTimerValue(length);
 1.11048 +};
 1.11049 +StkProactiveCmdHelperObject.prototype[COMPREHENSIONTLV_TAG_IMMEDIATE_RESPONSE] = function COMPREHENSIONTLV_TAG_IMMEDIATE_RESPONSE(length) {
 1.11050 +  return this.retrieveImmediaResponse(length);
 1.11051 +};
 1.11052 +StkProactiveCmdHelperObject.prototype[COMPREHENSIONTLV_TAG_URL] = function COMPREHENSIONTLV_TAG_URL(length) {
 1.11053 +  return this.retrieveUrl(length);
 1.11054 +};
 1.11055 +StkProactiveCmdHelperObject.prototype[COMPREHENSIONTLV_TAG_NEXT_ACTION_IND] = function COMPREHENSIONTLV_TAG_NEXT_ACTION_IND(length) {
 1.11056 +  return this.retrieveNextActionList(length);
 1.11057 +};
 1.11058 +
 1.11059 +function ComprehensionTlvHelperObject(aContext) {
 1.11060 +  this.context = aContext;
 1.11061 +}
 1.11062 +ComprehensionTlvHelperObject.prototype = {
 1.11063 +  context: null,
 1.11064 +
 1.11065 +  /**
 1.11066 +   * Decode raw data to a Comprehension-TLV.
 1.11067 +   */
 1.11068 +  decode: function() {
 1.11069 +    let GsmPDUHelper = this.context.GsmPDUHelper;
 1.11070 +
 1.11071 +    let hlen = 0; // For header(tag field + length field) length.
 1.11072 +    let temp = GsmPDUHelper.readHexOctet();
 1.11073 +    hlen++;
 1.11074 +
 1.11075 +    // TS 101.220, clause 7.1.1
 1.11076 +    let tag, cr;
 1.11077 +    switch (temp) {
 1.11078 +      // TS 101.220, clause 7.1.1
 1.11079 +      case 0x0: // Not used.
 1.11080 +      case 0xff: // Not used.
 1.11081 +      case 0x80: // Reserved for future use.
 1.11082 +        throw new Error("Invalid octet when parsing Comprehension TLV :" + temp);
 1.11083 +      case 0x7f: // Tag is three byte format.
 1.11084 +        // TS 101.220 clause 7.1.1.2.
 1.11085 +        // | Byte 1 | Byte 2                        | Byte 3 |
 1.11086 +        // |        | 8 | 7 | 6 | 5 | 4 | 3 | 2 | 1 |        |
 1.11087 +        // | 0x7f   |CR | Tag Value                          |
 1.11088 +        tag = (GsmPDUHelper.readHexOctet() << 8) | GsmPDUHelper.readHexOctet();
 1.11089 +        hlen += 2;
 1.11090 +        cr = (tag & 0x8000) !== 0;
 1.11091 +        tag &= ~0x8000;
 1.11092 +        break;
 1.11093 +      default: // Tag is single byte format.
 1.11094 +        tag = temp;
 1.11095 +        // TS 101.220 clause 7.1.1.1.
 1.11096 +        // | 8 | 7 | 6 | 5 | 4 | 3 | 2 | 1 |
 1.11097 +        // |CR | Tag Value                 |
 1.11098 +        cr = (tag & 0x80) !== 0;
 1.11099 +        tag &= ~0x80;
 1.11100 +    }
 1.11101 +
 1.11102 +    // TS 101.220 clause 7.1.2, Length Encoding.
 1.11103 +    // Length   |  Byte 1  | Byte 2 | Byte 3 | Byte 4 |
 1.11104 +    // 0 - 127  |  00 - 7f | N/A    | N/A    | N/A    |
 1.11105 +    // 128-255  |  81      | 80 - ff| N/A    | N/A    |
 1.11106 +    // 256-65535|  82      | 0100 - ffff     | N/A    |
 1.11107 +    // 65536-   |  83      |     010000 - ffffff      |
 1.11108 +    // 16777215
 1.11109 +    //
 1.11110 +    // Length errors: TS 11.14, clause 6.10.6
 1.11111 +
 1.11112 +    let length; // Data length.
 1.11113 +    temp = GsmPDUHelper.readHexOctet();
 1.11114 +    hlen++;
 1.11115 +    if (temp < 0x80) {
 1.11116 +      length = temp;
 1.11117 +    } else if (temp == 0x81) {
 1.11118 +      length = GsmPDUHelper.readHexOctet();
 1.11119 +      hlen++;
 1.11120 +      if (length < 0x80) {
 1.11121 +        throw new Error("Invalid length in Comprehension TLV :" + length);
 1.11122 +      }
 1.11123 +    } else if (temp == 0x82) {
 1.11124 +      length = (GsmPDUHelper.readHexOctet() << 8) | GsmPDUHelper.readHexOctet();
 1.11125 +      hlen += 2;
 1.11126 +      if (lenth < 0x0100) {
 1.11127 +        throw new Error("Invalid length in 3-byte Comprehension TLV :" + length);
 1.11128 +      }
 1.11129 +    } else if (temp == 0x83) {
 1.11130 +      length = (GsmPDUHelper.readHexOctet() << 16) |
 1.11131 +               (GsmPDUHelper.readHexOctet() << 8)  |
 1.11132 +                GsmPDUHelper.readHexOctet();
 1.11133 +      hlen += 3;
 1.11134 +      if (length < 0x010000) {
 1.11135 +        throw new Error("Invalid length in 4-byte Comprehension TLV :" + length);
 1.11136 +      }
 1.11137 +    } else {
 1.11138 +      throw new Error("Invalid octet in Comprehension TLV :" + temp);
 1.11139 +    }
 1.11140 +
 1.11141 +    let ctlv = {
 1.11142 +      tag: tag,
 1.11143 +      length: length,
 1.11144 +      value: this.context.StkProactiveCmdHelper.retrieve(tag, length),
 1.11145 +      cr: cr,
 1.11146 +      hlen: hlen
 1.11147 +    };
 1.11148 +    return ctlv;
 1.11149 +  },
 1.11150 +
 1.11151 +  decodeChunks: function(length) {
 1.11152 +    let chunks = [];
 1.11153 +    let index = 0;
 1.11154 +    while (index < length) {
 1.11155 +      let tlv = this.decode();
 1.11156 +      chunks.push(tlv);
 1.11157 +      index += tlv.length;
 1.11158 +      index += tlv.hlen;
 1.11159 +    }
 1.11160 +    return chunks;
 1.11161 +  },
 1.11162 +
 1.11163 +  /**
 1.11164 +   * Write Location Info Comprehension TLV.
 1.11165 +   *
 1.11166 +   * @param loc location Information.
 1.11167 +   */
 1.11168 +  writeLocationInfoTlv: function(loc) {
 1.11169 +    let GsmPDUHelper = this.context.GsmPDUHelper;
 1.11170 +
 1.11171 +    GsmPDUHelper.writeHexOctet(COMPREHENSIONTLV_TAG_LOCATION_INFO |
 1.11172 +                               COMPREHENSIONTLV_FLAG_CR);
 1.11173 +    GsmPDUHelper.writeHexOctet(loc.gsmCellId > 0xffff ? 9 : 7);
 1.11174 +    // From TS 11.14, clause 12.19
 1.11175 +    // "The mobile country code (MCC), the mobile network code (MNC),
 1.11176 +    // the location area code (LAC) and the cell ID are
 1.11177 +    // coded as in TS 04.08."
 1.11178 +    // And from TS 04.08 and TS 24.008,
 1.11179 +    // the format is as follows:
 1.11180 +    //
 1.11181 +    // MCC = MCC_digit_1 + MCC_digit_2 + MCC_digit_3
 1.11182 +    //
 1.11183 +    //   8  7  6  5    4  3  2  1
 1.11184 +    // +-------------+-------------+
 1.11185 +    // | MCC digit 2 | MCC digit 1 | octet 2
 1.11186 +    // | MNC digit 3 | MCC digit 3 | octet 3
 1.11187 +    // | MNC digit 2 | MNC digit 1 | octet 4
 1.11188 +    // +-------------+-------------+
 1.11189 +    //
 1.11190 +    // Also in TS 24.008
 1.11191 +    // "However a network operator may decide to
 1.11192 +    // use only two digits in the MNC in the LAI over the
 1.11193 +    // radio interface. In this case, bits 5 to 8 of octet 3
 1.11194 +    // shall be coded as '1111'".
 1.11195 +
 1.11196 +    // MCC & MNC, 3 octets
 1.11197 +    let mcc = loc.mcc, mnc;
 1.11198 +    if (loc.mnc.length == 2) {
 1.11199 +      mnc = "F" + loc.mnc;
 1.11200 +    } else {
 1.11201 +      mnc = loc.mnc[2] + loc.mnc[0] + loc.mnc[1];
 1.11202 +    }
 1.11203 +    GsmPDUHelper.writeSwappedNibbleBCD(mcc + mnc);
 1.11204 +
 1.11205 +    // LAC, 2 octets
 1.11206 +    GsmPDUHelper.writeHexOctet((loc.gsmLocationAreaCode >> 8) & 0xff);
 1.11207 +    GsmPDUHelper.writeHexOctet(loc.gsmLocationAreaCode & 0xff);
 1.11208 +
 1.11209 +    // Cell Id
 1.11210 +    if (loc.gsmCellId > 0xffff) {
 1.11211 +      // UMTS/WCDMA, gsmCellId is 28 bits.
 1.11212 +      GsmPDUHelper.writeHexOctet((loc.gsmCellId >> 24) & 0xff);
 1.11213 +      GsmPDUHelper.writeHexOctet((loc.gsmCellId >> 16) & 0xff);
 1.11214 +      GsmPDUHelper.writeHexOctet((loc.gsmCellId >> 8) & 0xff);
 1.11215 +      GsmPDUHelper.writeHexOctet(loc.gsmCellId & 0xff);
 1.11216 +    } else {
 1.11217 +      // GSM, gsmCellId is 16 bits.
 1.11218 +      GsmPDUHelper.writeHexOctet((loc.gsmCellId >> 8) & 0xff);
 1.11219 +      GsmPDUHelper.writeHexOctet(loc.gsmCellId & 0xff);
 1.11220 +    }
 1.11221 +  },
 1.11222 +
 1.11223 +  /**
 1.11224 +   * Given a geckoError string, this function translates it into cause value
 1.11225 +   * and write the value into buffer.
 1.11226 +   *
 1.11227 +   * @param geckoError Error string that is passed to gecko.
 1.11228 +   */
 1.11229 +  writeCauseTlv: function(geckoError) {
 1.11230 +    let GsmPDUHelper = this.context.GsmPDUHelper;
 1.11231 +
 1.11232 +    let cause = -1;
 1.11233 +    for (let errorNo in RIL_ERROR_TO_GECKO_ERROR) {
 1.11234 +      if (geckoError == RIL_ERROR_TO_GECKO_ERROR[errorNo]) {
 1.11235 +        cause = errorNo;
 1.11236 +        break;
 1.11237 +      }
 1.11238 +    }
 1.11239 +    cause = (cause == -1) ? ERROR_SUCCESS : cause;
 1.11240 +
 1.11241 +    GsmPDUHelper.writeHexOctet(COMPREHENSIONTLV_TAG_CAUSE |
 1.11242 +                               COMPREHENSIONTLV_FLAG_CR);
 1.11243 +    GsmPDUHelper.writeHexOctet(2);  // For single cause value.
 1.11244 +
 1.11245 +    // TS 04.08, clause 10.5.4.11: National standard code + user location.
 1.11246 +    GsmPDUHelper.writeHexOctet(0x60);
 1.11247 +
 1.11248 +    // TS 04.08, clause 10.5.4.11: ext bit = 1 + 7 bits for cause.
 1.11249 +    // +-----------------+----------------------------------+
 1.11250 +    // | Ext = 1 (1 bit) |          Cause (7 bits)          |
 1.11251 +    // +-----------------+----------------------------------+
 1.11252 +    GsmPDUHelper.writeHexOctet(0x80 | cause);
 1.11253 +  },
 1.11254 +
 1.11255 +  writeDateTimeZoneTlv: function(date) {
 1.11256 +    let GsmPDUHelper = this.context.GsmPDUHelper;
 1.11257 +
 1.11258 +    GsmPDUHelper.writeHexOctet(COMPREHENSIONTLV_TAG_DATE_TIME_ZONE);
 1.11259 +    GsmPDUHelper.writeHexOctet(7);
 1.11260 +    GsmPDUHelper.writeTimestamp(date);
 1.11261 +  },
 1.11262 +
 1.11263 +  writeLanguageTlv: function(language) {
 1.11264 +    let GsmPDUHelper = this.context.GsmPDUHelper;
 1.11265 +
 1.11266 +    GsmPDUHelper.writeHexOctet(COMPREHENSIONTLV_TAG_LANGUAGE);
 1.11267 +    GsmPDUHelper.writeHexOctet(2);
 1.11268 +
 1.11269 +    // ISO 639-1, Alpha-2 code
 1.11270 +    // TS 123.038, clause 6.2.1, GSM 7 bit Default Alphabet
 1.11271 +    GsmPDUHelper.writeHexOctet(
 1.11272 +      PDU_NL_LOCKING_SHIFT_TABLES[PDU_NL_IDENTIFIER_DEFAULT].indexOf(language[0]));
 1.11273 +    GsmPDUHelper.writeHexOctet(
 1.11274 +      PDU_NL_LOCKING_SHIFT_TABLES[PDU_NL_IDENTIFIER_DEFAULT].indexOf(language[1]));
 1.11275 +  },
 1.11276 +
 1.11277 +  /**
 1.11278 +   * Write Timer Value Comprehension TLV.
 1.11279 +   *
 1.11280 +   * @param seconds length of time during of the timer.
 1.11281 +   * @param cr Comprehension Required or not
 1.11282 +   */
 1.11283 +  writeTimerValueTlv: function(seconds, cr) {
 1.11284 +    let GsmPDUHelper = this.context.GsmPDUHelper;
 1.11285 +
 1.11286 +    GsmPDUHelper.writeHexOctet(COMPREHENSIONTLV_TAG_TIMER_VALUE |
 1.11287 +                               (cr ? COMPREHENSIONTLV_FLAG_CR : 0));
 1.11288 +    GsmPDUHelper.writeHexOctet(3);
 1.11289 +
 1.11290 +    // TS 102.223, clause 8.38
 1.11291 +    // +----------------+------------------+-------------------+
 1.11292 +    // | hours (1 byte) | minutes (1 btye) | secounds (1 byte) |
 1.11293 +    // +----------------+------------------+-------------------+
 1.11294 +    GsmPDUHelper.writeSwappedNibbleBCDNum(Math.floor(seconds / 60 / 60));
 1.11295 +    GsmPDUHelper.writeSwappedNibbleBCDNum(Math.floor(seconds / 60) % 60);
 1.11296 +    GsmPDUHelper.writeSwappedNibbleBCDNum(seconds % 60);
 1.11297 +  },
 1.11298 +
 1.11299 +  getSizeOfLengthOctets: function(length) {
 1.11300 +    if (length >= 0x10000) {
 1.11301 +      return 4; // 0x83, len_1, len_2, len_3
 1.11302 +    } else if (length >= 0x100) {
 1.11303 +      return 3; // 0x82, len_1, len_2
 1.11304 +    } else if (length >= 0x80) {
 1.11305 +      return 2; // 0x81, len
 1.11306 +    } else {
 1.11307 +      return 1; // len
 1.11308 +    }
 1.11309 +  },
 1.11310 +
 1.11311 +  writeLength: function(length) {
 1.11312 +    let GsmPDUHelper = this.context.GsmPDUHelper;
 1.11313 +
 1.11314 +    // TS 101.220 clause 7.1.2, Length Encoding.
 1.11315 +    // Length   |  Byte 1  | Byte 2 | Byte 3 | Byte 4 |
 1.11316 +    // 0 - 127  |  00 - 7f | N/A    | N/A    | N/A    |
 1.11317 +    // 128-255  |  81      | 80 - ff| N/A    | N/A    |
 1.11318 +    // 256-65535|  82      | 0100 - ffff     | N/A    |
 1.11319 +    // 65536-   |  83      |     010000 - ffffff      |
 1.11320 +    // 16777215
 1.11321 +    if (length < 0x80) {
 1.11322 +      GsmPDUHelper.writeHexOctet(length);
 1.11323 +    } else if (0x80 <= length && length < 0x100) {
 1.11324 +      GsmPDUHelper.writeHexOctet(0x81);
 1.11325 +      GsmPDUHelper.writeHexOctet(length);
 1.11326 +    } else if (0x100 <= length && length < 0x10000) {
 1.11327 +      GsmPDUHelper.writeHexOctet(0x82);
 1.11328 +      GsmPDUHelper.writeHexOctet((length >> 8) & 0xff);
 1.11329 +      GsmPDUHelper.writeHexOctet(length & 0xff);
 1.11330 +    } else if (0x10000 <= length && length < 0x1000000) {
 1.11331 +      GsmPDUHelper.writeHexOctet(0x83);
 1.11332 +      GsmPDUHelper.writeHexOctet((length >> 16) & 0xff);
 1.11333 +      GsmPDUHelper.writeHexOctet((length >> 8) & 0xff);
 1.11334 +      GsmPDUHelper.writeHexOctet(length & 0xff);
 1.11335 +    } else {
 1.11336 +      throw new Error("Invalid length value :" + length);
 1.11337 +    }
 1.11338 +  },
 1.11339 +};
 1.11340 +
 1.11341 +function BerTlvHelperObject(aContext) {
 1.11342 +  this.context = aContext;
 1.11343 +}
 1.11344 +BerTlvHelperObject.prototype = {
 1.11345 +  context: null,
 1.11346 +
 1.11347 +  /**
 1.11348 +   * Decode Ber TLV.
 1.11349 +   *
 1.11350 +   * @param dataLen
 1.11351 +   *        The length of data in bytes.
 1.11352 +   */
 1.11353 +  decode: function(dataLen) {
 1.11354 +    let GsmPDUHelper = this.context.GsmPDUHelper;
 1.11355 +
 1.11356 +    let hlen = 0;
 1.11357 +    let tag = GsmPDUHelper.readHexOctet();
 1.11358 +    hlen++;
 1.11359 +
 1.11360 +    // The length is coded onto 1 or 2 bytes.
 1.11361 +    // Length    | Byte 1                | Byte 2
 1.11362 +    // 0 - 127   | length ('00' to '7f') | N/A
 1.11363 +    // 128 - 255 | '81'                  | length ('80' to 'ff')
 1.11364 +    let length;
 1.11365 +    let temp = GsmPDUHelper.readHexOctet();
 1.11366 +    hlen++;
 1.11367 +    if (temp < 0x80) {
 1.11368 +      length = temp;
 1.11369 +    } else if (temp === 0x81) {
 1.11370 +      length = GsmPDUHelper.readHexOctet();
 1.11371 +      hlen++;
 1.11372 +      if (length < 0x80) {
 1.11373 +        throw new Error("Invalid length " + length);
 1.11374 +      }
 1.11375 +    } else {
 1.11376 +      throw new Error("Invalid length octet " + temp);
 1.11377 +    }
 1.11378 +
 1.11379 +    // Header + body length check.
 1.11380 +    if (dataLen - hlen !== length) {
 1.11381 +      throw new Error("Unexpected BerTlvHelper value length!!");
 1.11382 +    }
 1.11383 +
 1.11384 +    let method = this[tag];
 1.11385 +    if (typeof method != "function") {
 1.11386 +      throw new Error("Unknown Ber tag 0x" + tag.toString(16));
 1.11387 +    }
 1.11388 +
 1.11389 +    let value = method.call(this, length);
 1.11390 +
 1.11391 +    return {
 1.11392 +      tag: tag,
 1.11393 +      length: length,
 1.11394 +      value: value
 1.11395 +    };
 1.11396 +  },
 1.11397 +
 1.11398 +  /**
 1.11399 +   * Process the value part for FCP template TLV.
 1.11400 +   *
 1.11401 +   * @param length
 1.11402 +   *        The length of data in bytes.
 1.11403 +   */
 1.11404 +  processFcpTemplate: function(length) {
 1.11405 +    let tlvs = this.decodeChunks(length);
 1.11406 +    return tlvs;
 1.11407 +  },
 1.11408 +
 1.11409 +  /**
 1.11410 +   * Process the value part for proactive command TLV.
 1.11411 +   *
 1.11412 +   * @param length
 1.11413 +   *        The length of data in bytes.
 1.11414 +   */
 1.11415 +  processProactiveCommand: function(length) {
 1.11416 +    let ctlvs = this.context.ComprehensionTlvHelper.decodeChunks(length);
 1.11417 +    return ctlvs;
 1.11418 +  },
 1.11419 +
 1.11420 +  /**
 1.11421 +   * Decode raw data to a Ber-TLV.
 1.11422 +   */
 1.11423 +  decodeInnerTlv: function() {
 1.11424 +    let GsmPDUHelper = this.context.GsmPDUHelper;
 1.11425 +    let tag = GsmPDUHelper.readHexOctet();
 1.11426 +    let length = GsmPDUHelper.readHexOctet();
 1.11427 +    return {
 1.11428 +      tag: tag,
 1.11429 +      length: length,
 1.11430 +      value: this.retrieve(tag, length)
 1.11431 +    };
 1.11432 +  },
 1.11433 +
 1.11434 +  decodeChunks: function(length) {
 1.11435 +    let chunks = [];
 1.11436 +    let index = 0;
 1.11437 +    while (index < length) {
 1.11438 +      let tlv = this.decodeInnerTlv();
 1.11439 +      if (tlv.value) {
 1.11440 +        chunks.push(tlv);
 1.11441 +      }
 1.11442 +      index += tlv.length;
 1.11443 +      // tag + length fields consume 2 bytes.
 1.11444 +      index += 2;
 1.11445 +    }
 1.11446 +    return chunks;
 1.11447 +  },
 1.11448 +
 1.11449 +  retrieve: function(tag, length) {
 1.11450 +    let method = this[tag];
 1.11451 +    if (typeof method != "function") {
 1.11452 +      if (DEBUG) {
 1.11453 +        this.context.debug("Unknown Ber tag : 0x" + tag.toString(16));
 1.11454 +      }
 1.11455 +      let Buf = this.context.Buf;
 1.11456 +      Buf.seekIncoming(length * Buf.PDU_HEX_OCTET_SIZE);
 1.11457 +      return null;
 1.11458 +    }
 1.11459 +    return method.call(this, length);
 1.11460 +  },
 1.11461 +
 1.11462 +  /**
 1.11463 +   * File Size Data.
 1.11464 +   *
 1.11465 +   * | Byte        | Description                                | Length |
 1.11466 +   * |  1          | Tag                                        |   1    |
 1.11467 +   * |  2          | Length                                     |   1    |
 1.11468 +   * |  3 to X+24  | Number of allocated data bytes in the file |   X    |
 1.11469 +   * |             | , excluding structural information         |        |
 1.11470 +   */
 1.11471 +  retrieveFileSizeData: function(length) {
 1.11472 +    let GsmPDUHelper = this.context.GsmPDUHelper;
 1.11473 +    let fileSizeData = 0;
 1.11474 +    for (let i = 0; i < length; i++) {
 1.11475 +      fileSizeData = fileSizeData << 8;
 1.11476 +      fileSizeData += GsmPDUHelper.readHexOctet();
 1.11477 +    }
 1.11478 +
 1.11479 +    return {fileSizeData: fileSizeData};
 1.11480 +  },
 1.11481 +
 1.11482 +  /**
 1.11483 +   * File Descriptor.
 1.11484 +   *
 1.11485 +   * | Byte    | Description           | Length |
 1.11486 +   * |  1      | Tag                   |   1    |
 1.11487 +   * |  2      | Length                |   1    |
 1.11488 +   * |  3      | File descriptor byte  |   1    |
 1.11489 +   * |  4      | Data coding byte      |   1    |
 1.11490 +   * |  5 ~ 6  | Record length         |   2    |
 1.11491 +   * |  7      | Number of records     |   1    |
 1.11492 +   */
 1.11493 +  retrieveFileDescriptor: function(length) {
 1.11494 +    let GsmPDUHelper = this.context.GsmPDUHelper;
 1.11495 +    let fileDescriptorByte = GsmPDUHelper.readHexOctet();
 1.11496 +    let dataCodingByte = GsmPDUHelper.readHexOctet();
 1.11497 +    // See TS 102 221 Table 11.5, we only care the least 3 bits for the
 1.11498 +    // structure of file.
 1.11499 +    let fileStructure = fileDescriptorByte & 0x07;
 1.11500 +
 1.11501 +    let fileDescriptor = {
 1.11502 +      fileStructure: fileStructure
 1.11503 +    };
 1.11504 +    // byte 5 ~ 7 are mandatory for linear fixed and cyclic files, otherwise
 1.11505 +    // they are not applicable.
 1.11506 +    if (fileStructure === UICC_EF_STRUCTURE[EF_TYPE_LINEAR_FIXED] ||
 1.11507 +        fileStructure === UICC_EF_STRUCTURE[EF_TYPE_CYCLIC]) {
 1.11508 +      fileDescriptor.recordLength = (GsmPDUHelper.readHexOctet() << 8) +
 1.11509 +                                     GsmPDUHelper.readHexOctet();
 1.11510 +      fileDescriptor.numOfRecords = GsmPDUHelper.readHexOctet();
 1.11511 +    }
 1.11512 +
 1.11513 +    return fileDescriptor;
 1.11514 +  },
 1.11515 +
 1.11516 +  /**
 1.11517 +   * File identifier.
 1.11518 +   *
 1.11519 +   * | Byte    | Description      | Length |
 1.11520 +   * |  1      | Tag              |   1    |
 1.11521 +   * |  2      | Length           |   1    |
 1.11522 +   * |  3 ~ 4  | File identifier  |   2    |
 1.11523 +   */
 1.11524 +  retrieveFileIdentifier: function(length) {
 1.11525 +    let GsmPDUHelper = this.context.GsmPDUHelper;
 1.11526 +    return {fileId : (GsmPDUHelper.readHexOctet() << 8) +
 1.11527 +                      GsmPDUHelper.readHexOctet()};
 1.11528 +  },
 1.11529 +
 1.11530 +  searchForNextTag: function(tag, iter) {
 1.11531 +    for (let [index, tlv] in iter) {
 1.11532 +      if (tlv.tag === tag) {
 1.11533 +        return tlv;
 1.11534 +      }
 1.11535 +    }
 1.11536 +    return null;
 1.11537 +  }
 1.11538 +};
 1.11539 +BerTlvHelperObject.prototype[BER_FCP_TEMPLATE_TAG] = function BER_FCP_TEMPLATE_TAG(length) {
 1.11540 +  return this.processFcpTemplate(length);
 1.11541 +};
 1.11542 +BerTlvHelperObject.prototype[BER_PROACTIVE_COMMAND_TAG] = function BER_PROACTIVE_COMMAND_TAG(length) {
 1.11543 +  return this.processProactiveCommand(length);
 1.11544 +};
 1.11545 +BerTlvHelperObject.prototype[BER_FCP_FILE_SIZE_DATA_TAG] = function BER_FCP_FILE_SIZE_DATA_TAG(length) {
 1.11546 +  return this.retrieveFileSizeData(length);
 1.11547 +};
 1.11548 +BerTlvHelperObject.prototype[BER_FCP_FILE_DESCRIPTOR_TAG] = function BER_FCP_FILE_DESCRIPTOR_TAG(length) {
 1.11549 +  return this.retrieveFileDescriptor(length);
 1.11550 +};
 1.11551 +BerTlvHelperObject.prototype[BER_FCP_FILE_IDENTIFIER_TAG] = function BER_FCP_FILE_IDENTIFIER_TAG(length) {
 1.11552 +  return this.retrieveFileIdentifier(length);
 1.11553 +};
 1.11554 +
 1.11555 +/**
 1.11556 + * ICC Helper for getting EF path.
 1.11557 + */
 1.11558 +function ICCFileHelperObject(aContext) {
 1.11559 +  this.context = aContext;
 1.11560 +}
 1.11561 +ICCFileHelperObject.prototype = {
 1.11562 +  context: null,
 1.11563 +
 1.11564 +  /**
 1.11565 +   * This function handles only EFs that are common to RUIM, SIM, USIM
 1.11566 +   * and other types of ICC cards.
 1.11567 +   */
 1.11568 +  getCommonEFPath: function(fileId) {
 1.11569 +    switch (fileId) {
 1.11570 +      case ICC_EF_ICCID:
 1.11571 +        return EF_PATH_MF_SIM;
 1.11572 +      case ICC_EF_ADN:
 1.11573 +        return EF_PATH_MF_SIM + EF_PATH_DF_TELECOM;
 1.11574 +      case ICC_EF_PBR:
 1.11575 +        return EF_PATH_MF_SIM + EF_PATH_DF_TELECOM + EF_PATH_DF_PHONEBOOK;
 1.11576 +    }
 1.11577 +    return null;
 1.11578 +  },
 1.11579 +
 1.11580 +  /**
 1.11581 +   * This function handles EFs for SIM.
 1.11582 +   */
 1.11583 +  getSimEFPath: function(fileId) {
 1.11584 +    switch (fileId) {
 1.11585 +      case ICC_EF_FDN:
 1.11586 +      case ICC_EF_MSISDN:
 1.11587 +      case ICC_EF_SMS:
 1.11588 +        return EF_PATH_MF_SIM + EF_PATH_DF_TELECOM;
 1.11589 +      case ICC_EF_AD:
 1.11590 +      case ICC_EF_MBDN:
 1.11591 +      case ICC_EF_MWIS:
 1.11592 +      case ICC_EF_PLMNsel:
 1.11593 +      case ICC_EF_SPN:
 1.11594 +      case ICC_EF_SPDI:
 1.11595 +      case ICC_EF_SST:
 1.11596 +      case ICC_EF_PHASE:
 1.11597 +      case ICC_EF_CBMI:
 1.11598 +      case ICC_EF_CBMID:
 1.11599 +      case ICC_EF_CBMIR:
 1.11600 +      case ICC_EF_OPL:
 1.11601 +      case ICC_EF_PNN:
 1.11602 +        return EF_PATH_MF_SIM + EF_PATH_DF_GSM;
 1.11603 +      default:
 1.11604 +        return null;
 1.11605 +    }
 1.11606 +  },
 1.11607 +
 1.11608 +  /**
 1.11609 +   * This function handles EFs for USIM.
 1.11610 +   */
 1.11611 +  getUSimEFPath: function(fileId) {
 1.11612 +    switch (fileId) {
 1.11613 +      case ICC_EF_AD:
 1.11614 +      case ICC_EF_FDN:
 1.11615 +      case ICC_EF_MBDN:
 1.11616 +      case ICC_EF_MWIS:
 1.11617 +      case ICC_EF_UST:
 1.11618 +      case ICC_EF_MSISDN:
 1.11619 +      case ICC_EF_SPN:
 1.11620 +      case ICC_EF_SPDI:
 1.11621 +      case ICC_EF_CBMI:
 1.11622 +      case ICC_EF_CBMID:
 1.11623 +      case ICC_EF_CBMIR:
 1.11624 +      case ICC_EF_OPL:
 1.11625 +      case ICC_EF_PNN:
 1.11626 +      case ICC_EF_SMS:
 1.11627 +        return EF_PATH_MF_SIM + EF_PATH_ADF_USIM;
 1.11628 +      default:
 1.11629 +        // The file ids in USIM phone book entries are decided by the
 1.11630 +        // card manufacturer. So if we don't match any of the cases
 1.11631 +        // above and if its a USIM return the phone book path.
 1.11632 +        return EF_PATH_MF_SIM + EF_PATH_DF_TELECOM + EF_PATH_DF_PHONEBOOK;
 1.11633 +    }
 1.11634 +  },
 1.11635 +
 1.11636 +  /**
 1.11637 +   * This function handles EFs for RUIM
 1.11638 +   */
 1.11639 +  getRuimEFPath: function(fileId) {
 1.11640 +    switch(fileId) {
 1.11641 +      case ICC_EF_CSIM_IMSI_M:
 1.11642 +      case ICC_EF_CSIM_CDMAHOME:
 1.11643 +      case ICC_EF_CSIM_CST:
 1.11644 +      case ICC_EF_CSIM_SPN:
 1.11645 +        return EF_PATH_MF_SIM + EF_PATH_DF_CDMA;
 1.11646 +      case ICC_EF_FDN:
 1.11647 +        return EF_PATH_MF_SIM + EF_PATH_DF_TELECOM;
 1.11648 +      default:
 1.11649 +        return null;
 1.11650 +    }
 1.11651 +  },
 1.11652 +
 1.11653 +  /**
 1.11654 +   * Helper function for getting the pathId for the specific ICC record
 1.11655 +   * depeding on which type of ICC card we are using.
 1.11656 +   *
 1.11657 +   * @param fileId
 1.11658 +   *        File id.
 1.11659 +   * @return The pathId or null in case of an error or invalid input.
 1.11660 +   */
 1.11661 +  getEFPath: function(fileId) {
 1.11662 +    let appType = this.context.RIL.appType;
 1.11663 +    if (appType == null) {
 1.11664 +      return null;
 1.11665 +    }
 1.11666 +
 1.11667 +    let path = this.getCommonEFPath(fileId);
 1.11668 +    if (path) {
 1.11669 +      return path;
 1.11670 +    }
 1.11671 +
 1.11672 +    switch (appType) {
 1.11673 +      case CARD_APPTYPE_SIM:
 1.11674 +        return this.getSimEFPath(fileId);
 1.11675 +      case CARD_APPTYPE_USIM:
 1.11676 +        return this.getUSimEFPath(fileId);
 1.11677 +      case CARD_APPTYPE_RUIM:
 1.11678 +        return this.getRuimEFPath(fileId);
 1.11679 +      default:
 1.11680 +        return null;
 1.11681 +    }
 1.11682 +  }
 1.11683 +};
 1.11684 +
 1.11685 +/**
 1.11686 + * Helper for ICC IO functionalities.
 1.11687 + */
 1.11688 +function ICCIOHelperObject(aContext) {
 1.11689 +  this.context = aContext;
 1.11690 +}
 1.11691 +ICCIOHelperObject.prototype = {
 1.11692 +  context: null,
 1.11693 +
 1.11694 +  /**
 1.11695 +   * Load EF with type 'Linear Fixed'.
 1.11696 +   *
 1.11697 +   * @param fileId
 1.11698 +   *        The file to operate on, one of the ICC_EF_* constants.
 1.11699 +   * @param recordNumber [optional]
 1.11700 +   *        The number of the record shall be loaded.
 1.11701 +   * @param recordSize [optional]
 1.11702 +   *        The size of the record.
 1.11703 +   * @param callback [optional]
 1.11704 +   *        The callback function shall be called when the record(s) is read.
 1.11705 +   * @param onerror [optional]
 1.11706 +   *        The callback function shall be called when failure.
 1.11707 +   */
 1.11708 +  loadLinearFixedEF: function(options) {
 1.11709 +    let cb;
 1.11710 +    let readRecord = (function(options) {
 1.11711 +      options.command = ICC_COMMAND_READ_RECORD;
 1.11712 +      options.p1 = options.recordNumber || 1; // Record number
 1.11713 +      options.p2 = READ_RECORD_ABSOLUTE_MODE;
 1.11714 +      options.p3 = options.recordSize;
 1.11715 +      options.callback = cb || options.callback;
 1.11716 +      this.context.RIL.iccIO(options);
 1.11717 +    }).bind(this);
 1.11718 +
 1.11719 +    options.type = EF_TYPE_LINEAR_FIXED;
 1.11720 +    options.pathId = this.context.ICCFileHelper.getEFPath(options.fileId);
 1.11721 +    if (options.recordSize) {
 1.11722 +      readRecord(options);
 1.11723 +      return;
 1.11724 +    }
 1.11725 +
 1.11726 +    cb = options.callback;
 1.11727 +    options.callback = readRecord;
 1.11728 +    this.getResponse(options);
 1.11729 +  },
 1.11730 +
 1.11731 +  /**
 1.11732 +   * Load next record from current record Id.
 1.11733 +   */
 1.11734 +  loadNextRecord: function(options) {
 1.11735 +    options.p1++;
 1.11736 +    this.context.RIL.iccIO(options);
 1.11737 +  },
 1.11738 +
 1.11739 +  /**
 1.11740 +   * Update EF with type 'Linear Fixed'.
 1.11741 +   *
 1.11742 +   * @param fileId
 1.11743 +   *        The file to operate on, one of the ICC_EF_* constants.
 1.11744 +   * @param recordNumber
 1.11745 +   *        The number of the record shall be updated.
 1.11746 +   * @param dataWriter [optional]
 1.11747 +   *        The function for writing string parameter for the ICC_COMMAND_UPDATE_RECORD.
 1.11748 +   * @param pin2 [optional]
 1.11749 +   *        PIN2 is required when updating ICC_EF_FDN.
 1.11750 +   * @param callback [optional]
 1.11751 +   *        The callback function shall be called when the record is updated.
 1.11752 +   * @param onerror [optional]
 1.11753 +   *        The callback function shall be called when failure.
 1.11754 +   */
 1.11755 +  updateLinearFixedEF: function(options) {
 1.11756 +    if (!options.fileId || !options.recordNumber) {
 1.11757 +      throw new Error("Unexpected fileId " + options.fileId +
 1.11758 +                      " or recordNumber " + options.recordNumber);
 1.11759 +    }
 1.11760 +
 1.11761 +    options.type = EF_TYPE_LINEAR_FIXED;
 1.11762 +    options.pathId = this.context.ICCFileHelper.getEFPath(options.fileId);
 1.11763 +    let cb = options.callback;
 1.11764 +    options.callback = function callback(options) {
 1.11765 +      options.callback = cb;
 1.11766 +      options.command = ICC_COMMAND_UPDATE_RECORD;
 1.11767 +      options.p1 = options.recordNumber;
 1.11768 +      options.p2 = READ_RECORD_ABSOLUTE_MODE;
 1.11769 +      options.p3 = options.recordSize;
 1.11770 +      this.context.RIL.iccIO(options);
 1.11771 +    }.bind(this);
 1.11772 +    this.getResponse(options);
 1.11773 +  },
 1.11774 +
 1.11775 +  /**
 1.11776 +   * Load EF with type 'Transparent'.
 1.11777 +   *
 1.11778 +   * @param fileId
 1.11779 +   *        The file to operate on, one of the ICC_EF_* constants.
 1.11780 +   * @param callback [optional]
 1.11781 +   *        The callback function shall be called when the record(s) is read.
 1.11782 +   * @param onerror [optional]
 1.11783 +   *        The callback function shall be called when failure.
 1.11784 +   */
 1.11785 +  loadTransparentEF: function(options) {
 1.11786 +    options.type = EF_TYPE_TRANSPARENT;
 1.11787 +    let cb = options.callback;
 1.11788 +    options.callback = function callback(options) {
 1.11789 +      options.callback = cb;
 1.11790 +      options.command = ICC_COMMAND_READ_BINARY;
 1.11791 +      options.p3 = options.fileSize;
 1.11792 +      this.context.RIL.iccIO(options);
 1.11793 +    }.bind(this);
 1.11794 +    this.getResponse(options);
 1.11795 +  },
 1.11796 +
 1.11797 +  /**
 1.11798 +   * Use ICC_COMMAND_GET_RESPONSE to query the EF.
 1.11799 +   *
 1.11800 +   * @param fileId
 1.11801 +   *        The file to operate on, one of the ICC_EF_* constants.
 1.11802 +   */
 1.11803 +  getResponse: function(options) {
 1.11804 +    options.command = ICC_COMMAND_GET_RESPONSE;
 1.11805 +    options.pathId = options.pathId ||
 1.11806 +                     this.context.ICCFileHelper.getEFPath(options.fileId);
 1.11807 +    if (!options.pathId) {
 1.11808 +      throw new Error("Unknown pathId for " + options.fileId.toString(16));
 1.11809 +    }
 1.11810 +    options.p1 = 0; // For GET_RESPONSE, p1 = 0
 1.11811 +    options.p2 = 0; // For GET_RESPONSE, p2 = 0
 1.11812 +    options.p3 = GET_RESPONSE_EF_SIZE_BYTES;
 1.11813 +    this.context.RIL.iccIO(options);
 1.11814 +  },
 1.11815 +
 1.11816 +  /**
 1.11817 +   * Process ICC I/O response.
 1.11818 +   */
 1.11819 +  processICCIO: function(options) {
 1.11820 +    let func = this[options.command];
 1.11821 +    func.call(this, options);
 1.11822 +  },
 1.11823 +
 1.11824 +  /**
 1.11825 +   * Process a ICC_COMMAND_GET_RESPONSE type command for REQUEST_SIM_IO.
 1.11826 +   */
 1.11827 +  processICCIOGetResponse: function(options) {
 1.11828 +    let Buf = this.context.Buf;
 1.11829 +    let strLen = Buf.readInt32();
 1.11830 +
 1.11831 +    let peek = this.context.GsmPDUHelper.readHexOctet();
 1.11832 +    Buf.seekIncoming(-1 * Buf.PDU_HEX_OCTET_SIZE);
 1.11833 +    if (peek === BER_FCP_TEMPLATE_TAG) {
 1.11834 +      this.processUSimGetResponse(options, strLen / 2);
 1.11835 +    } else {
 1.11836 +      this.processSimGetResponse(options);
 1.11837 +    }
 1.11838 +    Buf.readStringDelimiter(strLen);
 1.11839 +
 1.11840 +    if (options.callback) {
 1.11841 +      options.callback(options);
 1.11842 +    }
 1.11843 +  },
 1.11844 +
 1.11845 +  /**
 1.11846 +   * Helper function for processing USIM get response.
 1.11847 +   */
 1.11848 +  processUSimGetResponse: function(options, octetLen) {
 1.11849 +    let BerTlvHelper = this.context.BerTlvHelper;
 1.11850 +
 1.11851 +    let berTlv = BerTlvHelper.decode(octetLen);
 1.11852 +    // See TS 102 221 Table 11.4 for the content order of getResponse.
 1.11853 +    let iter = Iterator(berTlv.value);
 1.11854 +    let tlv = BerTlvHelper.searchForNextTag(BER_FCP_FILE_DESCRIPTOR_TAG,
 1.11855 +                                            iter);
 1.11856 +    if (!tlv || (tlv.value.fileStructure !== UICC_EF_STRUCTURE[options.type])) {
 1.11857 +      throw new Error("Expected EF type " + UICC_EF_STRUCTURE[options.type] +
 1.11858 +                      " but read " + tlv.value.fileStructure);
 1.11859 +    }
 1.11860 +
 1.11861 +    if (tlv.value.fileStructure === UICC_EF_STRUCTURE[EF_TYPE_LINEAR_FIXED] ||
 1.11862 +        tlv.value.fileStructure === UICC_EF_STRUCTURE[EF_TYPE_CYCLIC]) {
 1.11863 +      options.recordSize = tlv.value.recordLength;
 1.11864 +      options.totalRecords = tlv.value.numOfRecords;
 1.11865 +    }
 1.11866 +
 1.11867 +    tlv = BerTlvHelper.searchForNextTag(BER_FCP_FILE_IDENTIFIER_TAG, iter);
 1.11868 +    if (!tlv || (tlv.value.fileId !== options.fileId)) {
 1.11869 +      throw new Error("Expected file ID " + options.fileId.toString(16) +
 1.11870 +                      " but read " + fileId.toString(16));
 1.11871 +    }
 1.11872 +
 1.11873 +    tlv = BerTlvHelper.searchForNextTag(BER_FCP_FILE_SIZE_DATA_TAG, iter);
 1.11874 +    if (!tlv) {
 1.11875 +      throw new Error("Unexpected file size data");
 1.11876 +    }
 1.11877 +    options.fileSize = tlv.value.fileSizeData;
 1.11878 +  },
 1.11879 +
 1.11880 +  /**
 1.11881 +   * Helper function for processing SIM get response.
 1.11882 +   */
 1.11883 +  processSimGetResponse: function(options) {
 1.11884 +    let Buf = this.context.Buf;
 1.11885 +    let GsmPDUHelper = this.context.GsmPDUHelper;
 1.11886 +
 1.11887 +    // The format is from TS 51.011, clause 9.2.1
 1.11888 +
 1.11889 +    // Skip RFU, data[0] data[1].
 1.11890 +    Buf.seekIncoming(2 * Buf.PDU_HEX_OCTET_SIZE);
 1.11891 +
 1.11892 +    // File size, data[2], data[3]
 1.11893 +    options.fileSize = (GsmPDUHelper.readHexOctet() << 8) |
 1.11894 +                        GsmPDUHelper.readHexOctet();
 1.11895 +
 1.11896 +    // 2 bytes File id. data[4], data[5]
 1.11897 +    let fileId = (GsmPDUHelper.readHexOctet() << 8) |
 1.11898 +                  GsmPDUHelper.readHexOctet();
 1.11899 +    if (fileId != options.fileId) {
 1.11900 +      throw new Error("Expected file ID " + options.fileId.toString(16) +
 1.11901 +                      " but read " + fileId.toString(16));
 1.11902 +    }
 1.11903 +
 1.11904 +    // Type of file, data[6]
 1.11905 +    let fileType = GsmPDUHelper.readHexOctet();
 1.11906 +    if (fileType != TYPE_EF) {
 1.11907 +      throw new Error("Unexpected file type " + fileType);
 1.11908 +    }
 1.11909 +
 1.11910 +    // Skip 1 byte RFU, data[7],
 1.11911 +    //      3 bytes Access conditions, data[8] data[9] data[10],
 1.11912 +    //      1 byte File status, data[11],
 1.11913 +    //      1 byte Length of the following data, data[12].
 1.11914 +    Buf.seekIncoming(((RESPONSE_DATA_STRUCTURE - RESPONSE_DATA_FILE_TYPE - 1) *
 1.11915 +        Buf.PDU_HEX_OCTET_SIZE));
 1.11916 +
 1.11917 +    // Read Structure of EF, data[13]
 1.11918 +    let efType = GsmPDUHelper.readHexOctet();
 1.11919 +    if (efType != options.type) {
 1.11920 +      throw new Error("Expected EF type " + options.type + " but read " + efType);
 1.11921 +    }
 1.11922 +
 1.11923 +    // TODO: Bug 952025.
 1.11924 +    // Length of a record, data[14].
 1.11925 +    // Only available for LINEAR_FIXED and CYCLIC.
 1.11926 +    if (efType == EF_TYPE_LINEAR_FIXED || efType == EF_TYPE_CYCLIC) {
 1.11927 +      options.recordSize = GsmPDUHelper.readHexOctet();
 1.11928 +      options.totalRecords = options.fileSize / options.recordSize;
 1.11929 +    } else {
 1.11930 +      Buf.seekIncoming(1 * Buf.PDU_HEX_OCTET_SIZE);
 1.11931 +    }
 1.11932 +  },
 1.11933 +
 1.11934 +  /**
 1.11935 +   * Process a ICC_COMMAND_READ_RECORD type command for REQUEST_SIM_IO.
 1.11936 +   */
 1.11937 +  processICCIOReadRecord: function(options) {
 1.11938 +    if (options.callback) {
 1.11939 +      options.callback(options);
 1.11940 +    }
 1.11941 +  },
 1.11942 +
 1.11943 +  /**
 1.11944 +   * Process a ICC_COMMAND_READ_BINARY type command for REQUEST_SIM_IO.
 1.11945 +   */
 1.11946 +  processICCIOReadBinary: function(options) {
 1.11947 +    if (options.callback) {
 1.11948 +      options.callback(options);
 1.11949 +    }
 1.11950 +  },
 1.11951 +
 1.11952 +  /**
 1.11953 +   * Process a ICC_COMMAND_UPDATE_RECORD type command for REQUEST_SIM_IO.
 1.11954 +   */
 1.11955 +  processICCIOUpdateRecord: function(options) {
 1.11956 +    if (options.callback) {
 1.11957 +      options.callback(options);
 1.11958 +    }
 1.11959 +  },
 1.11960 +
 1.11961 +  /**
 1.11962 +   * Process ICC IO error.
 1.11963 +   */
 1.11964 +  processICCIOError: function(options) {
 1.11965 +    let requestError = RIL_ERROR_TO_GECKO_ERROR[options.rilRequestError];
 1.11966 +    if (DEBUG) {
 1.11967 +      // See GSM11.11, TS 51.011 clause 9.4, and ISO 7816-4 for the error
 1.11968 +      // description.
 1.11969 +      let errorMsg = "ICC I/O Error code " + requestError +
 1.11970 +                     " EF id = " + options.fileId.toString(16) +
 1.11971 +                     " command = " + options.command.toString(16);
 1.11972 +      if (options.sw1 && options.sw2) {
 1.11973 +        errorMsg += "(" + options.sw1.toString(16) +
 1.11974 +                    "/" + options.sw2.toString(16) + ")";
 1.11975 +      }
 1.11976 +      this.context.debug(errorMsg);
 1.11977 +    }
 1.11978 +    if (options.onerror) {
 1.11979 +      options.onerror(requestError);
 1.11980 +    }
 1.11981 +  },
 1.11982 +};
 1.11983 +ICCIOHelperObject.prototype[ICC_COMMAND_SEEK] = null;
 1.11984 +ICCIOHelperObject.prototype[ICC_COMMAND_READ_BINARY] = function ICC_COMMAND_READ_BINARY(options) {
 1.11985 +  this.processICCIOReadBinary(options);
 1.11986 +};
 1.11987 +ICCIOHelperObject.prototype[ICC_COMMAND_READ_RECORD] = function ICC_COMMAND_READ_RECORD(options) {
 1.11988 +  this.processICCIOReadRecord(options);
 1.11989 +};
 1.11990 +ICCIOHelperObject.prototype[ICC_COMMAND_GET_RESPONSE] = function ICC_COMMAND_GET_RESPONSE(options) {
 1.11991 +  this.processICCIOGetResponse(options);
 1.11992 +};
 1.11993 +ICCIOHelperObject.prototype[ICC_COMMAND_UPDATE_BINARY] = null;
 1.11994 +ICCIOHelperObject.prototype[ICC_COMMAND_UPDATE_RECORD] = function ICC_COMMAND_UPDATE_RECORD(options) {
 1.11995 +  this.processICCIOUpdateRecord(options);
 1.11996 +};
 1.11997 +
 1.11998 +/**
 1.11999 + * Helper for ICC records.
 1.12000 + */
 1.12001 +function ICCRecordHelperObject(aContext) {
 1.12002 +  this.context = aContext;
 1.12003 +}
 1.12004 +ICCRecordHelperObject.prototype = {
 1.12005 +  context: null,
 1.12006 +
 1.12007 +  /**
 1.12008 +   * Fetch ICC records.
 1.12009 +   */
 1.12010 +  fetchICCRecords: function() {
 1.12011 +    switch (this.context.RIL.appType) {
 1.12012 +      case CARD_APPTYPE_SIM:
 1.12013 +      case CARD_APPTYPE_USIM:
 1.12014 +        this.context.SimRecordHelper.fetchSimRecords();
 1.12015 +        break;
 1.12016 +      case CARD_APPTYPE_RUIM:
 1.12017 +        this.context.RuimRecordHelper.fetchRuimRecords();
 1.12018 +        break;
 1.12019 +    }
 1.12020 +  },
 1.12021 +
 1.12022 +  /**
 1.12023 +   * Read the ICCID.
 1.12024 +   */
 1.12025 +  readICCID: function() {
 1.12026 +    function callback() {
 1.12027 +      let Buf = this.context.Buf;
 1.12028 +      let RIL = this.context.RIL;
 1.12029 +
 1.12030 +      let strLen = Buf.readInt32();
 1.12031 +      let octetLen = strLen / 2;
 1.12032 +      RIL.iccInfo.iccid =
 1.12033 +        this.context.GsmPDUHelper.readSwappedNibbleBcdString(octetLen, true);
 1.12034 +      // Consumes the remaining buffer if any.
 1.12035 +      let unReadBuffer = this.context.Buf.getReadAvailable() -
 1.12036 +                         this.context.Buf.PDU_HEX_OCTET_SIZE;
 1.12037 +      if (unReadBuffer > 0) {
 1.12038 +        this.context.Buf.seekIncoming(unReadBuffer);
 1.12039 +      }
 1.12040 +      Buf.readStringDelimiter(strLen);
 1.12041 +
 1.12042 +      if (DEBUG) this.context.debug("ICCID: " + RIL.iccInfo.iccid);
 1.12043 +      if (RIL.iccInfo.iccid) {
 1.12044 +        this.context.ICCUtilsHelper.handleICCInfoChange();
 1.12045 +        RIL.reportStkServiceIsRunning();
 1.12046 +      }
 1.12047 +    }
 1.12048 +
 1.12049 +    this.context.ICCIOHelper.loadTransparentEF({
 1.12050 +      fileId: ICC_EF_ICCID,
 1.12051 +      callback: callback.bind(this)
 1.12052 +    });
 1.12053 +  },
 1.12054 +
 1.12055 +  /**
 1.12056 +   * Read ICC ADN like EF, i.e. EF_ADN, EF_FDN.
 1.12057 +   *
 1.12058 +   * @param fileId      EF id of the ADN or FDN.
 1.12059 +   * @param onsuccess   Callback to be called when success.
 1.12060 +   * @param onerror     Callback to be called when error.
 1.12061 +   */
 1.12062 +  readADNLike: function(fileId, onsuccess, onerror) {
 1.12063 +    let ICCIOHelper = this.context.ICCIOHelper;
 1.12064 +
 1.12065 +    function callback(options) {
 1.12066 +      let contact =
 1.12067 +        this.context.ICCPDUHelper.readAlphaIdDiallingNumber(options.recordSize);
 1.12068 +      if (contact) {
 1.12069 +        contact.recordId = options.p1;
 1.12070 +        contacts.push(contact);
 1.12071 +      }
 1.12072 +
 1.12073 +      if (options.p1 < options.totalRecords) {
 1.12074 +        ICCIOHelper.loadNextRecord(options);
 1.12075 +      } else {
 1.12076 +        if (DEBUG) {
 1.12077 +          for (let i = 0; i < contacts.length; i++) {
 1.12078 +            this.context.debug("contact [" + i + "] " +
 1.12079 +                               JSON.stringify(contacts[i]));
 1.12080 +          }
 1.12081 +        }
 1.12082 +        if (onsuccess) {
 1.12083 +          onsuccess(contacts);
 1.12084 +        }
 1.12085 +      }
 1.12086 +    }
 1.12087 +
 1.12088 +    let contacts = [];
 1.12089 +    ICCIOHelper.loadLinearFixedEF({fileId: fileId,
 1.12090 +                                   callback: callback.bind(this),
 1.12091 +                                   onerror: onerror});
 1.12092 +  },
 1.12093 +
 1.12094 +  /**
 1.12095 +   * Update ICC ADN like EFs, like EF_ADN, EF_FDN.
 1.12096 +   *
 1.12097 +   * @param fileId      EF id of the ADN or FDN.
 1.12098 +   * @param contact     The contact will be updated. (Shall have recordId property)
 1.12099 +   * @param pin2        PIN2 is required when updating ICC_EF_FDN.
 1.12100 +   * @param onsuccess   Callback to be called when success.
 1.12101 +   * @param onerror     Callback to be called when error.
 1.12102 +   */
 1.12103 +  updateADNLike: function(fileId, contact, pin2, onsuccess, onerror) {
 1.12104 +    function dataWriter(recordSize) {
 1.12105 +      this.context.ICCPDUHelper.writeAlphaIdDiallingNumber(recordSize,
 1.12106 +                                                           contact.alphaId,
 1.12107 +                                                           contact.number);
 1.12108 +    }
 1.12109 +
 1.12110 +    function callback(options) {
 1.12111 +      if (onsuccess) {
 1.12112 +        onsuccess();
 1.12113 +      }
 1.12114 +    }
 1.12115 +
 1.12116 +    if (!contact || !contact.recordId) {
 1.12117 +      if (onerror) onerror(GECKO_ERROR_INVALID_PARAMETER);
 1.12118 +      return;
 1.12119 +    }
 1.12120 +
 1.12121 +    this.context.ICCIOHelper.updateLinearFixedEF({
 1.12122 +      fileId: fileId,
 1.12123 +      recordNumber: contact.recordId,
 1.12124 +      dataWriter: dataWriter.bind(this),
 1.12125 +      pin2: pin2,
 1.12126 +      callback: callback.bind(this),
 1.12127 +      onerror: onerror
 1.12128 +    });
 1.12129 +  },
 1.12130 +
 1.12131 +  /**
 1.12132 +   * Read USIM/RUIM Phonebook.
 1.12133 +   *
 1.12134 +   * @param onsuccess   Callback to be called when success.
 1.12135 +   * @param onerror     Callback to be called when error.
 1.12136 +   */
 1.12137 +  readPBR: function(onsuccess, onerror) {
 1.12138 +    let Buf = this.context.Buf;
 1.12139 +    let GsmPDUHelper = this.context.GsmPDUHelper;
 1.12140 +    let ICCIOHelper = this.context.ICCIOHelper;
 1.12141 +    let ICCUtilsHelper = this.context.ICCUtilsHelper;
 1.12142 +    let RIL = this.context.RIL;
 1.12143 +
 1.12144 +    function callback(options) {
 1.12145 +      let strLen = Buf.readInt32();
 1.12146 +      let octetLen = strLen / 2, readLen = 0;
 1.12147 +
 1.12148 +      let pbrTlvs = [];
 1.12149 +      while (readLen < octetLen) {
 1.12150 +        let tag = GsmPDUHelper.readHexOctet();
 1.12151 +        if (tag == 0xff) {
 1.12152 +          readLen++;
 1.12153 +          Buf.seekIncoming((octetLen - readLen) * Buf.PDU_HEX_OCTET_SIZE);
 1.12154 +          break;
 1.12155 +        }
 1.12156 +
 1.12157 +        let tlvLen = GsmPDUHelper.readHexOctet();
 1.12158 +        let tlvs = ICCUtilsHelper.decodeSimTlvs(tlvLen);
 1.12159 +        pbrTlvs.push({tag: tag,
 1.12160 +                      length: tlvLen,
 1.12161 +                      value: tlvs});
 1.12162 +
 1.12163 +        readLen += tlvLen + 2; // +2 for tag and tlvLen
 1.12164 +      }
 1.12165 +      Buf.readStringDelimiter(strLen);
 1.12166 +
 1.12167 +      if (pbrTlvs.length > 0) {
 1.12168 +        let pbr = ICCUtilsHelper.parsePbrTlvs(pbrTlvs);
 1.12169 +        // EF_ADN is mandatory if and only if DF_PHONEBOOK is present.
 1.12170 +        if (!pbr.adn) {
 1.12171 +          if (onerror) onerror("Cannot access ADN.");
 1.12172 +          return;
 1.12173 +        }
 1.12174 +        pbrs.push(pbr);
 1.12175 +      }
 1.12176 +
 1.12177 +      if (options.p1 < options.totalRecords) {
 1.12178 +        ICCIOHelper.loadNextRecord(options);
 1.12179 +      } else {
 1.12180 +        if (onsuccess) {
 1.12181 +          RIL.iccInfoPrivate.pbrs = pbrs;
 1.12182 +          onsuccess(pbrs);
 1.12183 +        }
 1.12184 +      }
 1.12185 +    }
 1.12186 +
 1.12187 +    if (RIL.iccInfoPrivate.pbrs) {
 1.12188 +      onsuccess(RIL.iccInfoPrivate.pbrs);
 1.12189 +      return;
 1.12190 +    }
 1.12191 +
 1.12192 +    let pbrs = [];
 1.12193 +    ICCIOHelper.loadLinearFixedEF({fileId : ICC_EF_PBR,
 1.12194 +                                   callback: callback.bind(this),
 1.12195 +                                   onerror: onerror});
 1.12196 +  },
 1.12197 +
 1.12198 +  /**
 1.12199 +   * Cache EF_IAP record size.
 1.12200 +   */
 1.12201 +  _iapRecordSize: null,
 1.12202 +
 1.12203 +  /**
 1.12204 +   * Read ICC EF_IAP. (Index Administration Phonebook)
 1.12205 +   *
 1.12206 +   * @see TS 131.102, clause 4.4.2.2
 1.12207 +   *
 1.12208 +   * @param fileId       EF id of the IAP.
 1.12209 +   * @param recordNumber The number of the record shall be loaded.
 1.12210 +   * @param onsuccess    Callback to be called when success.
 1.12211 +   * @param onerror      Callback to be called when error.
 1.12212 +   */
 1.12213 +  readIAP: function(fileId, recordNumber, onsuccess, onerror) {
 1.12214 +    function callback(options) {
 1.12215 +      let Buf = this.context.Buf;
 1.12216 +      let strLen = Buf.readInt32();
 1.12217 +      let octetLen = strLen / 2;
 1.12218 +      this._iapRecordSize = options.recordSize;
 1.12219 +
 1.12220 +      let iap = this.context.GsmPDUHelper.readHexOctetArray(octetLen);
 1.12221 +      Buf.readStringDelimiter(strLen);
 1.12222 +
 1.12223 +      if (onsuccess) {
 1.12224 +        onsuccess(iap);
 1.12225 +      }
 1.12226 +    }
 1.12227 +
 1.12228 +    this.context.ICCIOHelper.loadLinearFixedEF({
 1.12229 +      fileId: fileId,
 1.12230 +      recordNumber: recordNumber,
 1.12231 +      recordSize: this._iapRecordSize,
 1.12232 +      callback: callback.bind(this),
 1.12233 +      onerror: onerror
 1.12234 +    });
 1.12235 +  },
 1.12236 +
 1.12237 +  /**
 1.12238 +   * Update USIM/RUIM Phonebook EF_IAP.
 1.12239 +   *
 1.12240 +   * @see TS 131.102, clause 4.4.2.13
 1.12241 +   *
 1.12242 +   * @param fileId       EF id of the IAP.
 1.12243 +   * @param recordNumber The identifier of the record shall be updated.
 1.12244 +   * @param iap          The IAP value to be written.
 1.12245 +   * @param onsuccess    Callback to be called when success.
 1.12246 +   * @param onerror      Callback to be called when error.
 1.12247 +   */
 1.12248 +  updateIAP: function(fileId, recordNumber, iap, onsuccess, onerror) {
 1.12249 +    let dataWriter = function dataWriter(recordSize) {
 1.12250 +      let Buf = this.context.Buf;
 1.12251 +      let GsmPDUHelper = this.context.GsmPDUHelper;
 1.12252 +
 1.12253 +      // Write String length
 1.12254 +      let strLen = recordSize * 2;
 1.12255 +      Buf.writeInt32(strLen);
 1.12256 +
 1.12257 +      for (let i = 0; i < iap.length; i++) {
 1.12258 +        GsmPDUHelper.writeHexOctet(iap[i]);
 1.12259 +      }
 1.12260 +
 1.12261 +      Buf.writeStringDelimiter(strLen);
 1.12262 +    }.bind(this);
 1.12263 +
 1.12264 +    this.context.ICCIOHelper.updateLinearFixedEF({
 1.12265 +      fileId: fileId,
 1.12266 +      recordNumber: recordNumber,
 1.12267 +      dataWriter: dataWriter,
 1.12268 +      callback: onsuccess,
 1.12269 +      onerror: onerror
 1.12270 +    });
 1.12271 +  },
 1.12272 +
 1.12273 +  /**
 1.12274 +   * Cache EF_Email record size.
 1.12275 +   */
 1.12276 +  _emailRecordSize: null,
 1.12277 +
 1.12278 +  /**
 1.12279 +   * Read USIM/RUIM Phonebook EF_EMAIL.
 1.12280 +   *
 1.12281 +   * @see TS 131.102, clause 4.4.2.13
 1.12282 +   *
 1.12283 +   * @param fileId       EF id of the EMAIL.
 1.12284 +   * @param fileType     The type of the EMAIL, one of the ICC_USIM_TYPE* constants.
 1.12285 +   * @param recordNumber The number of the record shall be loaded.
 1.12286 +   * @param onsuccess    Callback to be called when success.
 1.12287 +   * @param onerror      Callback to be called when error.
 1.12288 +   */
 1.12289 +  readEmail: function(fileId, fileType, recordNumber, onsuccess, onerror) {
 1.12290 +    function callback(options) {
 1.12291 +      let Buf = this.context.Buf;
 1.12292 +      let ICCPDUHelper = this.context.ICCPDUHelper;
 1.12293 +
 1.12294 +      let strLen = Buf.readInt32();
 1.12295 +      let octetLen = strLen / 2;
 1.12296 +      let email = null;
 1.12297 +      this._emailRecordSize = options.recordSize;
 1.12298 +
 1.12299 +      // Read contact's email
 1.12300 +      //
 1.12301 +      // | Byte     | Description                 | Length | M/O
 1.12302 +      // | 1 ~ X    | E-mail Address              |   X    |  M
 1.12303 +      // | X+1      | ADN file SFI                |   1    |  C
 1.12304 +      // | X+2      | ADN file Record Identifier  |   1    |  C
 1.12305 +      // Note: The fields marked as C above are mandatort if the file
 1.12306 +      //       is not type 1 (as specified in EF_PBR)
 1.12307 +      if (fileType == ICC_USIM_TYPE1_TAG) {
 1.12308 +        email = ICCPDUHelper.read8BitUnpackedToString(octetLen);
 1.12309 +      } else {
 1.12310 +        email = ICCPDUHelper.read8BitUnpackedToString(octetLen - 2);
 1.12311 +
 1.12312 +        // Consumes the remaining buffer
 1.12313 +        Buf.seekIncoming(2 * Buf.PDU_HEX_OCTET_SIZE); // For ADN SFI and Record Identifier
 1.12314 +      }
 1.12315 +
 1.12316 +      Buf.readStringDelimiter(strLen);
 1.12317 +
 1.12318 +      if (onsuccess) {
 1.12319 +        onsuccess(email);
 1.12320 +      }
 1.12321 +    }
 1.12322 +
 1.12323 +    this.context.ICCIOHelper.loadLinearFixedEF({
 1.12324 +      fileId: fileId,
 1.12325 +      recordNumber: recordNumber,
 1.12326 +      recordSize: this._emailRecordSize,
 1.12327 +      callback: callback.bind(this),
 1.12328 +      onerror: onerror
 1.12329 +    });
 1.12330 +  },
 1.12331 +
 1.12332 +  /**
 1.12333 +   * Update USIM/RUIM Phonebook EF_EMAIL.
 1.12334 +   *
 1.12335 +   * @see TS 131.102, clause 4.4.2.13
 1.12336 +   *
 1.12337 +   * @param pbr          Phonebook Reference File.
 1.12338 +   * @param recordNumber The identifier of the record shall be updated.
 1.12339 +   * @param email        The value to be written.
 1.12340 +   * @param adnRecordId  The record Id of ADN, only needed if the fileType of Email is TYPE2.
 1.12341 +   * @param onsuccess    Callback to be called when success.
 1.12342 +   * @param onerror      Callback to be called when error.
 1.12343 +   */
 1.12344 +  updateEmail: function(pbr, recordNumber, email, adnRecordId, onsuccess, onerror) {
 1.12345 +    let fileId = pbr[USIM_PBR_EMAIL].fileId;
 1.12346 +    let fileType = pbr[USIM_PBR_EMAIL].fileType;
 1.12347 +    let dataWriter = function dataWriter(recordSize) {
 1.12348 +      let Buf = this.context.Buf;
 1.12349 +      let GsmPDUHelper = this.context.GsmPDUHelper;
 1.12350 +      let ICCPDUHelper = this.context.ICCPDUHelper;
 1.12351 +
 1.12352 +      // Write String length
 1.12353 +      let strLen = recordSize * 2;
 1.12354 +      Buf.writeInt32(strLen);
 1.12355 +
 1.12356 +      if (fileType == ICC_USIM_TYPE1_TAG) {
 1.12357 +        ICCPDUHelper.writeStringTo8BitUnpacked(recordSize, email);
 1.12358 +      } else {
 1.12359 +        ICCPDUHelper.writeStringTo8BitUnpacked(recordSize - 2, email);
 1.12360 +        GsmPDUHelper.writeHexOctet(pbr.adn.sfi || 0xff);
 1.12361 +        GsmPDUHelper.writeHexOctet(adnRecordId);
 1.12362 +      }
 1.12363 +
 1.12364 +      Buf.writeStringDelimiter(strLen);
 1.12365 +    }.bind(this);
 1.12366 +
 1.12367 +    this.context.ICCIOHelper.updateLinearFixedEF({
 1.12368 +      fileId: fileId,
 1.12369 +      recordNumber: recordNumber,
 1.12370 +      dataWriter: dataWriter,
 1.12371 +      callback: onsuccess,
 1.12372 +      onerror: onerror
 1.12373 +    });
 1.12374 +  },
 1.12375 +
 1.12376 +  /**
 1.12377 +   * Cache EF_ANR record size.
 1.12378 +   */
 1.12379 +  _anrRecordSize: null,
 1.12380 +
 1.12381 +  /**
 1.12382 +   * Read USIM/RUIM Phonebook EF_ANR.
 1.12383 +   *
 1.12384 +   * @see TS 131.102, clause 4.4.2.9
 1.12385 +   *
 1.12386 +   * @param fileId       EF id of the ANR.
 1.12387 +   * @param fileType     One of the ICC_USIM_TYPE* constants.
 1.12388 +   * @param recordNumber The number of the record shall be loaded.
 1.12389 +   * @param onsuccess    Callback to be called when success.
 1.12390 +   * @param onerror      Callback to be called when error.
 1.12391 +   */
 1.12392 +  readANR: function(fileId, fileType, recordNumber, onsuccess, onerror) {
 1.12393 +    function callback(options) {
 1.12394 +      let Buf = this.context.Buf;
 1.12395 +      let strLen = Buf.readInt32();
 1.12396 +      let number = null;
 1.12397 +      this._anrRecordSize = options.recordSize;
 1.12398 +
 1.12399 +      // Skip EF_AAS Record ID.
 1.12400 +      Buf.seekIncoming(1 * Buf.PDU_HEX_OCTET_SIZE);
 1.12401 +
 1.12402 +      number = this.context.ICCPDUHelper.readNumberWithLength();
 1.12403 +
 1.12404 +      // Skip 2 unused octets, CCP and EXT1.
 1.12405 +      Buf.seekIncoming(2 * Buf.PDU_HEX_OCTET_SIZE);
 1.12406 +
 1.12407 +      // For Type 2 there are two extra octets.
 1.12408 +      if (fileType == ICC_USIM_TYPE2_TAG) {
 1.12409 +        // Skip 2 unused octets, ADN SFI and Record Identifier.
 1.12410 +        Buf.seekIncoming(2 * Buf.PDU_HEX_OCTET_SIZE);
 1.12411 +      }
 1.12412 +
 1.12413 +      Buf.readStringDelimiter(strLen);
 1.12414 +
 1.12415 +      if (onsuccess) {
 1.12416 +        onsuccess(number);
 1.12417 +      }
 1.12418 +    }
 1.12419 +
 1.12420 +    this.context.ICCIOHelper.loadLinearFixedEF({
 1.12421 +      fileId: fileId,
 1.12422 +      recordNumber: recordNumber,
 1.12423 +      recordSize: this._anrRecordSize,
 1.12424 +      callback: callback.bind(this),
 1.12425 +      onerror: onerror
 1.12426 +    });
 1.12427 +  },
 1.12428 +
 1.12429 +  /**
 1.12430 +   * Update USIM/RUIM Phonebook EF_ANR.
 1.12431 +   *
 1.12432 +   * @see TS 131.102, clause 4.4.2.9
 1.12433 +   *
 1.12434 +   * @param pbr          Phonebook Reference File.
 1.12435 +   * @param recordNumber The identifier of the record shall be updated.
 1.12436 +   * @param number       The value to be written.
 1.12437 +   * @param adnRecordId  The record Id of ADN, only needed if the fileType of Email is TYPE2.
 1.12438 +   * @param onsuccess    Callback to be called when success.
 1.12439 +   * @param onerror      Callback to be called when error.
 1.12440 +   */
 1.12441 +  updateANR: function(pbr, recordNumber, number, adnRecordId, onsuccess, onerror) {
 1.12442 +    let fileId = pbr[USIM_PBR_ANR0].fileId;
 1.12443 +    let fileType = pbr[USIM_PBR_ANR0].fileType;
 1.12444 +    let dataWriter = function dataWriter(recordSize) {
 1.12445 +      let Buf = this.context.Buf;
 1.12446 +      let GsmPDUHelper = this.context.GsmPDUHelper;
 1.12447 +
 1.12448 +      // Write String length
 1.12449 +      let strLen = recordSize * 2;
 1.12450 +      Buf.writeInt32(strLen);
 1.12451 +
 1.12452 +      // EF_AAS record Id. Unused for now.
 1.12453 +      GsmPDUHelper.writeHexOctet(0xff);
 1.12454 +
 1.12455 +      this.context.ICCPDUHelper.writeNumberWithLength(number);
 1.12456 +
 1.12457 +      // Write unused octets 0xff, CCP and EXT1.
 1.12458 +      GsmPDUHelper.writeHexOctet(0xff);
 1.12459 +      GsmPDUHelper.writeHexOctet(0xff);
 1.12460 +
 1.12461 +      // For Type 2 there are two extra octets.
 1.12462 +      if (fileType == ICC_USIM_TYPE2_TAG) {
 1.12463 +        GsmPDUHelper.writeHexOctet(pbr.adn.sfi || 0xff);
 1.12464 +        GsmPDUHelper.writeHexOctet(adnRecordId);
 1.12465 +      }
 1.12466 +
 1.12467 +      Buf.writeStringDelimiter(strLen);
 1.12468 +    }.bind(this);
 1.12469 +
 1.12470 +    this.context.ICCIOHelper.updateLinearFixedEF({
 1.12471 +      fileId: fileId,
 1.12472 +      recordNumber: recordNumber,
 1.12473 +      dataWriter: dataWriter,
 1.12474 +      callback: onsuccess,
 1.12475 +      onerror: onerror
 1.12476 +    });
 1.12477 +  },
 1.12478 +
 1.12479 +  /**
 1.12480 +   * Find free record id.
 1.12481 +   *
 1.12482 +   * @param fileId      EF id.
 1.12483 +   * @param onsuccess   Callback to be called when success.
 1.12484 +   * @param onerror     Callback to be called when error.
 1.12485 +   */
 1.12486 +  findFreeRecordId: function(fileId, onsuccess, onerror) {
 1.12487 +    let ICCIOHelper = this.context.ICCIOHelper;
 1.12488 +
 1.12489 +    function callback(options) {
 1.12490 +      let Buf = this.context.Buf;
 1.12491 +      let GsmPDUHelper = this.context.GsmPDUHelper;
 1.12492 +
 1.12493 +      let strLen = Buf.readInt32();
 1.12494 +      let octetLen = strLen / 2;
 1.12495 +      let readLen = 0;
 1.12496 +
 1.12497 +      while (readLen < octetLen) {
 1.12498 +        let octet = GsmPDUHelper.readHexOctet();
 1.12499 +        readLen++;
 1.12500 +        if (octet != 0xff) {
 1.12501 +          break;
 1.12502 +        }
 1.12503 +      }
 1.12504 +
 1.12505 +      if (readLen == octetLen) {
 1.12506 +        // Find free record.
 1.12507 +        if (onsuccess) {
 1.12508 +          onsuccess(options.p1);
 1.12509 +        }
 1.12510 +        return;
 1.12511 +      } else {
 1.12512 +        Buf.seekIncoming((octetLen - readLen) * Buf.PDU_HEX_OCTET_SIZE);
 1.12513 +      }
 1.12514 +
 1.12515 +      Buf.readStringDelimiter(strLen);
 1.12516 +
 1.12517 +      if (options.p1 < options.totalRecords) {
 1.12518 +        ICCIOHelper.loadNextRecord(options);
 1.12519 +      } else {
 1.12520 +        // No free record found.
 1.12521 +        if (DEBUG) {
 1.12522 +          this.context.debug(CONTACT_ERR_NO_FREE_RECORD_FOUND);
 1.12523 +        }
 1.12524 +        onerror(CONTACT_ERR_NO_FREE_RECORD_FOUND);
 1.12525 +      }
 1.12526 +    }
 1.12527 +
 1.12528 +    ICCIOHelper.loadLinearFixedEF({fileId: fileId,
 1.12529 +                                   callback: callback.bind(this),
 1.12530 +                                   onerror: onerror});
 1.12531 +  },
 1.12532 +};
 1.12533 +
 1.12534 +/**
 1.12535 + * Helper for (U)SIM Records.
 1.12536 + */
 1.12537 +function SimRecordHelperObject(aContext) {
 1.12538 +  this.context = aContext;
 1.12539 +}
 1.12540 +SimRecordHelperObject.prototype = {
 1.12541 +  context: null,
 1.12542 +
 1.12543 +  /**
 1.12544 +   * Fetch (U)SIM records.
 1.12545 +   */
 1.12546 +  fetchSimRecords: function() {
 1.12547 +    this.context.RIL.getIMSI();
 1.12548 +    this.readAD();
 1.12549 +    this.readSST();
 1.12550 +  },
 1.12551 +
 1.12552 +  /**
 1.12553 +   * Read EF_phase.
 1.12554 +   * This EF is only available in SIM.
 1.12555 +   */
 1.12556 +  readSimPhase: function() {
 1.12557 +    function callback() {
 1.12558 +      let Buf = this.context.Buf;
 1.12559 +      let strLen = Buf.readInt32();
 1.12560 +
 1.12561 +      let GsmPDUHelper = this.context.GsmPDUHelper;
 1.12562 +      let phase = GsmPDUHelper.readHexOctet();
 1.12563 +      // If EF_phase is coded '03' or greater, an ME supporting STK shall
 1.12564 +      // perform the PROFILE DOWNLOAD procedure.
 1.12565 +      if (RILQUIRKS_SEND_STK_PROFILE_DOWNLOAD &&
 1.12566 +          phase >= ICC_PHASE_2_PROFILE_DOWNLOAD_REQUIRED) {
 1.12567 +        this.context.RIL.sendStkTerminalProfile(STK_SUPPORTED_TERMINAL_PROFILE);
 1.12568 +      }
 1.12569 +
 1.12570 +      Buf.readStringDelimiter(strLen);
 1.12571 +    }
 1.12572 +
 1.12573 +    this.context.ICCIOHelper.loadTransparentEF({
 1.12574 +      fileId: ICC_EF_PHASE,
 1.12575 +      callback: callback.bind(this)
 1.12576 +    });
 1.12577 +  },
 1.12578 +
 1.12579 +  /**
 1.12580 +   * Read the MSISDN from the (U)SIM.
 1.12581 +   */
 1.12582 +  readMSISDN: function() {
 1.12583 +    function callback(options) {
 1.12584 +      let RIL = this.context.RIL;
 1.12585 +
 1.12586 +      let contact =
 1.12587 +        this.context.ICCPDUHelper.readAlphaIdDiallingNumber(options.recordSize);
 1.12588 +      if (!contact ||
 1.12589 +          (RIL.iccInfo.msisdn !== undefined &&
 1.12590 +           RIL.iccInfo.msisdn === contact.number)) {
 1.12591 +        return;
 1.12592 +      }
 1.12593 +      RIL.iccInfo.msisdn = contact.number;
 1.12594 +      if (DEBUG) this.context.debug("MSISDN: " + RIL.iccInfo.msisdn);
 1.12595 +      this.context.ICCUtilsHelper.handleICCInfoChange();
 1.12596 +    }
 1.12597 +
 1.12598 +    this.context.ICCIOHelper.loadLinearFixedEF({
 1.12599 +      fileId: ICC_EF_MSISDN,
 1.12600 +      callback: callback.bind(this)
 1.12601 +    });
 1.12602 +  },
 1.12603 +
 1.12604 +  /**
 1.12605 +   * Read the AD (Administrative Data) from the (U)SIM.
 1.12606 +   */
 1.12607 +  readAD: function() {
 1.12608 +    function callback() {
 1.12609 +      let Buf = this.context.Buf;
 1.12610 +      let strLen = Buf.readInt32();
 1.12611 +      // Each octet is encoded into two chars.
 1.12612 +      let octetLen = strLen / 2;
 1.12613 +      let ad = this.context.GsmPDUHelper.readHexOctetArray(octetLen);
 1.12614 +      Buf.readStringDelimiter(strLen);
 1.12615 +
 1.12616 +      if (DEBUG) {
 1.12617 +        let str = "";
 1.12618 +        for (let i = 0; i < ad.length; i++) {
 1.12619 +          str += ad[i] + ", ";
 1.12620 +        }
 1.12621 +        this.context.debug("AD: " + str);
 1.12622 +      }
 1.12623 +
 1.12624 +      let ICCUtilsHelper = this.context.ICCUtilsHelper;
 1.12625 +      let RIL = this.context.RIL;
 1.12626 +      // The 4th byte of the response is the length of MNC.
 1.12627 +      let mccMnc = ICCUtilsHelper.parseMccMncFromImsi(RIL.iccInfoPrivate.imsi,
 1.12628 +                                                      ad && ad[3]);
 1.12629 +      if (mccMnc) {
 1.12630 +        RIL.iccInfo.mcc = mccMnc.mcc;
 1.12631 +        RIL.iccInfo.mnc = mccMnc.mnc;
 1.12632 +        ICCUtilsHelper.handleICCInfoChange();
 1.12633 +      }
 1.12634 +    }
 1.12635 +
 1.12636 +    this.context.ICCIOHelper.loadTransparentEF({
 1.12637 +      fileId: ICC_EF_AD,
 1.12638 +      callback: callback.bind(this)
 1.12639 +    });
 1.12640 +  },
 1.12641 +
 1.12642 +  /**
 1.12643 +   * Read the SPN (Service Provider Name) from the (U)SIM.
 1.12644 +   */
 1.12645 +  readSPN: function() {
 1.12646 +    function callback() {
 1.12647 +      let Buf = this.context.Buf;
 1.12648 +      let strLen = Buf.readInt32();
 1.12649 +      // Each octet is encoded into two chars.
 1.12650 +      let octetLen = strLen / 2;
 1.12651 +      let spnDisplayCondition = this.context.GsmPDUHelper.readHexOctet();
 1.12652 +      // Minus 1 because the first octet is used to store display condition.
 1.12653 +      let spn = this.context.ICCPDUHelper.readAlphaIdentifier(octetLen - 1);
 1.12654 +      Buf.readStringDelimiter(strLen);
 1.12655 +
 1.12656 +      if (DEBUG) {
 1.12657 +        this.context.debug("SPN: spn = " + spn +
 1.12658 +                           ", spnDisplayCondition = " + spnDisplayCondition);
 1.12659 +      }
 1.12660 +
 1.12661 +      let RIL = this.context.RIL;
 1.12662 +      RIL.iccInfoPrivate.spnDisplayCondition = spnDisplayCondition;
 1.12663 +      RIL.iccInfo.spn = spn;
 1.12664 +      let ICCUtilsHelper = this.context.ICCUtilsHelper;
 1.12665 +      ICCUtilsHelper.updateDisplayCondition();
 1.12666 +      ICCUtilsHelper.handleICCInfoChange();
 1.12667 +    }
 1.12668 +
 1.12669 +    this.context.ICCIOHelper.loadTransparentEF({
 1.12670 +      fileId: ICC_EF_SPN,
 1.12671 +      callback: callback.bind(this)
 1.12672 +    });
 1.12673 +  },
 1.12674 +
 1.12675 +  /**
 1.12676 +   * Read the (U)SIM Service Table from the (U)SIM.
 1.12677 +   */
 1.12678 +  readSST: function() {
 1.12679 +    function callback() {
 1.12680 +      let Buf = this.context.Buf;
 1.12681 +      let RIL = this.context.RIL;
 1.12682 +
 1.12683 +      let strLen = Buf.readInt32();
 1.12684 +      // Each octet is encoded into two chars.
 1.12685 +      let octetLen = strLen / 2;
 1.12686 +      let sst = this.context.GsmPDUHelper.readHexOctetArray(octetLen);
 1.12687 +      Buf.readStringDelimiter(strLen);
 1.12688 +      RIL.iccInfoPrivate.sst = sst;
 1.12689 +      if (DEBUG) {
 1.12690 +        let str = "";
 1.12691 +        for (let i = 0; i < sst.length; i++) {
 1.12692 +          str += sst[i] + ", ";
 1.12693 +        }
 1.12694 +        this.context.debug("SST: " + str);
 1.12695 +      }
 1.12696 +
 1.12697 +      let ICCUtilsHelper = this.context.ICCUtilsHelper;
 1.12698 +      if (ICCUtilsHelper.isICCServiceAvailable("MSISDN")) {
 1.12699 +        if (DEBUG) this.context.debug("MSISDN: MSISDN is available");
 1.12700 +        this.readMSISDN();
 1.12701 +      } else {
 1.12702 +        if (DEBUG) this.context.debug("MSISDN: MSISDN service is not available");
 1.12703 +      }
 1.12704 +
 1.12705 +      // Fetch SPN and PLMN list, if some of them are available.
 1.12706 +      if (ICCUtilsHelper.isICCServiceAvailable("SPN")) {
 1.12707 +        if (DEBUG) this.context.debug("SPN: SPN is available");
 1.12708 +        this.readSPN();
 1.12709 +      } else {
 1.12710 +        if (DEBUG) this.context.debug("SPN: SPN service is not available");
 1.12711 +      }
 1.12712 +
 1.12713 +      if (ICCUtilsHelper.isICCServiceAvailable("MDN")) {
 1.12714 +        if (DEBUG) this.context.debug("MDN: MDN available.");
 1.12715 +        this.readMBDN();
 1.12716 +      } else {
 1.12717 +        if (DEBUG) this.context.debug("MDN: MDN service is not available");
 1.12718 +      }
 1.12719 +
 1.12720 +      if (ICCUtilsHelper.isICCServiceAvailable("MWIS")) {
 1.12721 +        if (DEBUG) this.context.debug("MWIS: MWIS is available");
 1.12722 +        this.readMWIS();
 1.12723 +      } else {
 1.12724 +        if (DEBUG) this.context.debug("MWIS: MWIS is not available");
 1.12725 +      }
 1.12726 +
 1.12727 +      if (ICCUtilsHelper.isICCServiceAvailable("SPDI")) {
 1.12728 +        if (DEBUG) this.context.debug("SPDI: SPDI available.");
 1.12729 +        this.readSPDI();
 1.12730 +      } else {
 1.12731 +        if (DEBUG) this.context.debug("SPDI: SPDI service is not available");
 1.12732 +      }
 1.12733 +
 1.12734 +      if (ICCUtilsHelper.isICCServiceAvailable("PNN")) {
 1.12735 +        if (DEBUG) this.context.debug("PNN: PNN is available");
 1.12736 +        this.readPNN();
 1.12737 +      } else {
 1.12738 +        if (DEBUG) this.context.debug("PNN: PNN is not available");
 1.12739 +      }
 1.12740 +
 1.12741 +      if (ICCUtilsHelper.isICCServiceAvailable("OPL")) {
 1.12742 +        if (DEBUG) this.context.debug("OPL: OPL is available");
 1.12743 +        this.readOPL();
 1.12744 +      } else {
 1.12745 +        if (DEBUG) this.context.debug("OPL: OPL is not available");
 1.12746 +      }
 1.12747 +
 1.12748 +      if (ICCUtilsHelper.isICCServiceAvailable("CBMI")) {
 1.12749 +        this.readCBMI();
 1.12750 +      } else {
 1.12751 +        RIL.cellBroadcastConfigs.CBMI = null;
 1.12752 +      }
 1.12753 +      if (ICCUtilsHelper.isICCServiceAvailable("DATA_DOWNLOAD_SMS_CB")) {
 1.12754 +        this.readCBMID();
 1.12755 +      } else {
 1.12756 +        RIL.cellBroadcastConfigs.CBMID = null;
 1.12757 +      }
 1.12758 +      if (ICCUtilsHelper.isICCServiceAvailable("CBMIR")) {
 1.12759 +        this.readCBMIR();
 1.12760 +      } else {
 1.12761 +        RIL.cellBroadcastConfigs.CBMIR = null;
 1.12762 +      }
 1.12763 +      RIL._mergeAllCellBroadcastConfigs();
 1.12764 +    }
 1.12765 +
 1.12766 +    // ICC_EF_UST has the same value with ICC_EF_SST.
 1.12767 +    this.context.ICCIOHelper.loadTransparentEF({
 1.12768 +      fileId: ICC_EF_SST,
 1.12769 +      callback: callback.bind(this)
 1.12770 +    });
 1.12771 +  },
 1.12772 +
 1.12773 +  /**
 1.12774 +   * Read (U)SIM MBDN. (Mailbox Dialling Number)
 1.12775 +   *
 1.12776 +   * @see TS 131.102, clause 4.2.60
 1.12777 +   */
 1.12778 +  readMBDN: function() {
 1.12779 +    function callback(options) {
 1.12780 +      let RIL = this.context.RIL;
 1.12781 +      let contact =
 1.12782 +        this.context.ICCPDUHelper.readAlphaIdDiallingNumber(options.recordSize);
 1.12783 +      if (!contact ||
 1.12784 +          (RIL.iccInfoPrivate.mbdn !== undefined &&
 1.12785 +           RIL.iccInfoPrivate.mbdn === contact.number)) {
 1.12786 +        return;
 1.12787 +      }
 1.12788 +      RIL.iccInfoPrivate.mbdn = contact.number;
 1.12789 +      if (DEBUG) {
 1.12790 +        this.context.debug("MBDN, alphaId=" + contact.alphaId +
 1.12791 +                           " number=" + contact.number);
 1.12792 +      }
 1.12793 +      contact.rilMessageType = "iccmbdn";
 1.12794 +      RIL.sendChromeMessage(contact);
 1.12795 +    }
 1.12796 +
 1.12797 +    this.context.ICCIOHelper.loadLinearFixedEF({
 1.12798 +      fileId: ICC_EF_MBDN,
 1.12799 +      callback: callback.bind(this)
 1.12800 +    });
 1.12801 +  },
 1.12802 +
 1.12803 +  /**
 1.12804 +   * Read ICC MWIS. (Message Waiting Indication Status)
 1.12805 +   *
 1.12806 +   * @see TS 31.102, clause 4.2.63 for USIM and TS 51.011, clause 10.3.45 for SIM.
 1.12807 +   */
 1.12808 +  readMWIS: function() {
 1.12809 +    function callback(options) {
 1.12810 +      let Buf = this.context.Buf;
 1.12811 +      let RIL = this.context.RIL;
 1.12812 +
 1.12813 +      let strLen = Buf.readInt32();
 1.12814 +      // Each octet is encoded into two chars.
 1.12815 +      let octetLen = strLen / 2;
 1.12816 +      let mwis = this.context.GsmPDUHelper.readHexOctetArray(octetLen);
 1.12817 +      Buf.readStringDelimiter(strLen);
 1.12818 +      if (!mwis) {
 1.12819 +        return;
 1.12820 +      }
 1.12821 +      RIL.iccInfoPrivate.mwis = mwis; //Keep raw MWIS for updateMWIS()
 1.12822 +
 1.12823 +      let mwi = {};
 1.12824 +      // b8 b7 B6 b5 b4 b3 b2 b1   4.2.63, TS 31.102 version 11.6.0
 1.12825 +      //  |  |  |  |  |  |  |  |__ Voicemail
 1.12826 +      //  |  |  |  |  |  |  |_____ Fax
 1.12827 +      //  |  |  |  |  |  |________ Electronic Mail
 1.12828 +      //  |  |  |  |  |___________ Other
 1.12829 +      //  |  |  |  |______________ Videomail
 1.12830 +      //  |__|__|_________________ RFU
 1.12831 +      mwi.active = ((mwis[0] & 0x01) != 0);
 1.12832 +
 1.12833 +      if (mwi.active) {
 1.12834 +        // In TS 23.040 msgCount is in the range from 0 to 255.
 1.12835 +        // The value 255 shall be taken to mean 255 or greater.
 1.12836 +        //
 1.12837 +        // However, There is no definition about 0 when MWI is active.
 1.12838 +        //
 1.12839 +        // Normally, when mwi is active, the msgCount must be larger than 0.
 1.12840 +        // Refer to other reference phone,
 1.12841 +        // 0 is usually treated as UNKNOWN for storing 2nd level MWI status (DCS).
 1.12842 +        mwi.msgCount = (mwis[1] === 0) ? GECKO_VOICEMAIL_MESSAGE_COUNT_UNKNOWN
 1.12843 +                                       : mwis[1];
 1.12844 +      } else {
 1.12845 +        mwi.msgCount = 0;
 1.12846 +      }
 1.12847 +
 1.12848 +      RIL.sendChromeMessage({ rilMessageType: "iccmwis",
 1.12849 +                              mwi: mwi });
 1.12850 +    }
 1.12851 +
 1.12852 +    this.context.ICCIOHelper.loadLinearFixedEF({
 1.12853 +      fileId: ICC_EF_MWIS,
 1.12854 +      recordNumber: 1, // Get 1st Subscriber Profile.
 1.12855 +      callback: callback.bind(this)
 1.12856 +    });
 1.12857 +  },
 1.12858 +
 1.12859 +  /**
 1.12860 +   * Update ICC MWIS. (Message Waiting Indication Status)
 1.12861 +   *
 1.12862 +   * @see TS 31.102, clause 4.2.63 for USIM and TS 51.011, clause 10.3.45 for SIM.
 1.12863 +   */
 1.12864 +  updateMWIS: function(mwi) {
 1.12865 +    let RIL = this.context.RIL;
 1.12866 +    if (!RIL.iccInfoPrivate.mwis) {
 1.12867 +      return;
 1.12868 +    }
 1.12869 +
 1.12870 +    function dataWriter(recordSize) {
 1.12871 +      let mwis = RIL.iccInfoPrivate.mwis;
 1.12872 +
 1.12873 +      let msgCount =
 1.12874 +          (mwi.msgCount === GECKO_VOICEMAIL_MESSAGE_COUNT_UNKNOWN) ? 0 : mwi.msgCount;
 1.12875 +
 1.12876 +      [mwis[0], mwis[1]] = (mwi.active) ? [(mwis[0] | 0x01), msgCount]
 1.12877 +                                        : [(mwis[0] & 0xFE), 0];
 1.12878 +
 1.12879 +      let strLen = recordSize * 2;
 1.12880 +      let Buf = this.context.Buf;
 1.12881 +      Buf.writeInt32(strLen);
 1.12882 +
 1.12883 +      let GsmPDUHelper = this.context.GsmPDUHelper;
 1.12884 +      for (let i = 0; i < mwis.length; i++) {
 1.12885 +        GsmPDUHelper.writeHexOctet(mwis[i]);
 1.12886 +      }
 1.12887 +
 1.12888 +      Buf.writeStringDelimiter(strLen);
 1.12889 +    }
 1.12890 +
 1.12891 +    this.context.ICCIOHelper.updateLinearFixedEF({
 1.12892 +      fileId: ICC_EF_MWIS,
 1.12893 +      recordNumber: 1, // Update 1st Subscriber Profile.
 1.12894 +      dataWriter: dataWriter.bind(this)
 1.12895 +    });
 1.12896 +  },
 1.12897 +
 1.12898 +  /**
 1.12899 +   * Read the SPDI (Service Provider Display Information) from the (U)SIM.
 1.12900 +   *
 1.12901 +   * See TS 131.102 section 4.2.66 for USIM and TS 51.011 section 10.3.50
 1.12902 +   * for SIM.
 1.12903 +   */
 1.12904 +  readSPDI: function() {
 1.12905 +    function callback() {
 1.12906 +      let Buf = this.context.Buf;
 1.12907 +      let strLen = Buf.readInt32();
 1.12908 +      let octetLen = strLen / 2;
 1.12909 +      let readLen = 0;
 1.12910 +      let endLoop = false;
 1.12911 +
 1.12912 +      let RIL = this.context.RIL;
 1.12913 +      RIL.iccInfoPrivate.SPDI = null;
 1.12914 +
 1.12915 +      let GsmPDUHelper = this.context.GsmPDUHelper;
 1.12916 +      while ((readLen < octetLen) && !endLoop) {
 1.12917 +        let tlvTag = GsmPDUHelper.readHexOctet();
 1.12918 +        let tlvLen = GsmPDUHelper.readHexOctet();
 1.12919 +        readLen += 2; // For tag and length fields.
 1.12920 +        switch (tlvTag) {
 1.12921 +        case SPDI_TAG_SPDI:
 1.12922 +          // The value part itself is a TLV.
 1.12923 +          continue;
 1.12924 +        case SPDI_TAG_PLMN_LIST:
 1.12925 +          // This PLMN list is what we want.
 1.12926 +          RIL.iccInfoPrivate.SPDI = this.readPLMNEntries(tlvLen / 3);
 1.12927 +          readLen += tlvLen;
 1.12928 +          endLoop = true;
 1.12929 +          break;
 1.12930 +        default:
 1.12931 +          // We don't care about its content if its tag is not SPDI nor
 1.12932 +          // PLMN_LIST.
 1.12933 +          endLoop = true;
 1.12934 +          break;
 1.12935 +        }
 1.12936 +      }
 1.12937 +
 1.12938 +      // Consume unread octets.
 1.12939 +      Buf.seekIncoming((octetLen - readLen) * Buf.PDU_HEX_OCTET_SIZE);
 1.12940 +      Buf.readStringDelimiter(strLen);
 1.12941 +
 1.12942 +      if (DEBUG) {
 1.12943 +        this.context.debug("SPDI: " + JSON.stringify(RIL.iccInfoPrivate.SPDI));
 1.12944 +      }
 1.12945 +      let ICCUtilsHelper = this.context.ICCUtilsHelper;
 1.12946 +      if (ICCUtilsHelper.updateDisplayCondition()) {
 1.12947 +        ICCUtilsHelper.handleICCInfoChange();
 1.12948 +      }
 1.12949 +    }
 1.12950 +
 1.12951 +    // PLMN List is Servive 51 in USIM, EF_SPDI
 1.12952 +    this.context.ICCIOHelper.loadTransparentEF({
 1.12953 +      fileId: ICC_EF_SPDI,
 1.12954 +      callback: callback.bind(this)
 1.12955 +    });
 1.12956 +  },
 1.12957 +
 1.12958 +  _readCbmiHelper: function(which) {
 1.12959 +    let RIL = this.context.RIL;
 1.12960 +
 1.12961 +    function callback() {
 1.12962 +      let Buf = this.context.Buf;
 1.12963 +      let strLength = Buf.readInt32();
 1.12964 +
 1.12965 +      // Each Message Identifier takes two octets and each octet is encoded
 1.12966 +      // into two chars.
 1.12967 +      let numIds = strLength / 4, list = null;
 1.12968 +      if (numIds) {
 1.12969 +        list = [];
 1.12970 +        let GsmPDUHelper = this.context.GsmPDUHelper;
 1.12971 +        for (let i = 0, id; i < numIds; i++) {
 1.12972 +          id = GsmPDUHelper.readHexOctet() << 8 | GsmPDUHelper.readHexOctet();
 1.12973 +          // `Unused entries shall be set to 'FF FF'.`
 1.12974 +          if (id != 0xFFFF) {
 1.12975 +            list.push(id);
 1.12976 +            list.push(id + 1);
 1.12977 +          }
 1.12978 +        }
 1.12979 +      }
 1.12980 +      if (DEBUG) {
 1.12981 +        this.context.debug(which + ": " + JSON.stringify(list));
 1.12982 +      }
 1.12983 +
 1.12984 +      Buf.readStringDelimiter(strLength);
 1.12985 +
 1.12986 +      RIL.cellBroadcastConfigs[which] = list;
 1.12987 +      RIL._mergeAllCellBroadcastConfigs();
 1.12988 +    }
 1.12989 +
 1.12990 +    function onerror() {
 1.12991 +      RIL.cellBroadcastConfigs[which] = null;
 1.12992 +      RIL._mergeAllCellBroadcastConfigs();
 1.12993 +    }
 1.12994 +
 1.12995 +    let fileId = GLOBAL["ICC_EF_" + which];
 1.12996 +    this.context.ICCIOHelper.loadTransparentEF({
 1.12997 +      fileId: fileId,
 1.12998 +      callback: callback.bind(this),
 1.12999 +      onerror: onerror.bind(this)
 1.13000 +    });
 1.13001 +  },
 1.13002 +
 1.13003 +  /**
 1.13004 +   * Read EFcbmi (Cell Broadcast Message Identifier selection)
 1.13005 +   *
 1.13006 +   * @see 3GPP TS 31.102 v110.02.0 section 4.2.14 EFcbmi
 1.13007 +   * @see 3GPP TS 51.011 v5.0.0 section 10.3.13 EFcbmi
 1.13008 +   */
 1.13009 +  readCBMI: function() {
 1.13010 +    this._readCbmiHelper("CBMI");
 1.13011 +  },
 1.13012 +
 1.13013 +  /**
 1.13014 +   * Read EFcbmid (Cell Broadcast Message Identifier for Data Download)
 1.13015 +   *
 1.13016 +   * @see 3GPP TS 31.102 v110.02.0 section 4.2.20 EFcbmid
 1.13017 +   * @see 3GPP TS 51.011 v5.0.0 section 10.3.26 EFcbmid
 1.13018 +   */
 1.13019 +  readCBMID: function() {
 1.13020 +    this._readCbmiHelper("CBMID");
 1.13021 +  },
 1.13022 +
 1.13023 +  /**
 1.13024 +   * Read EFcbmir (Cell Broadcast Message Identifier Range selection)
 1.13025 +   *
 1.13026 +   * @see 3GPP TS 31.102 v110.02.0 section 4.2.22 EFcbmir
 1.13027 +   * @see 3GPP TS 51.011 v5.0.0 section 10.3.28 EFcbmir
 1.13028 +   */
 1.13029 +  readCBMIR: function() {
 1.13030 +    let RIL = this.context.RIL;
 1.13031 +
 1.13032 +    function callback() {
 1.13033 +      let Buf = this.context.Buf;
 1.13034 +      let strLength = Buf.readInt32();
 1.13035 +
 1.13036 +      // Each Message Identifier range takes four octets and each octet is
 1.13037 +      // encoded into two chars.
 1.13038 +      let numIds = strLength / 8, list = null;
 1.13039 +      if (numIds) {
 1.13040 +        list = [];
 1.13041 +        let GsmPDUHelper = this.context.GsmPDUHelper;
 1.13042 +        for (let i = 0, from, to; i < numIds; i++) {
 1.13043 +          // `Bytes one and two of each range identifier equal the lower value
 1.13044 +          // of a cell broadcast range, bytes three and four equal the upper
 1.13045 +          // value of a cell broadcast range.`
 1.13046 +          from = GsmPDUHelper.readHexOctet() << 8 | GsmPDUHelper.readHexOctet();
 1.13047 +          to = GsmPDUHelper.readHexOctet() << 8 | GsmPDUHelper.readHexOctet();
 1.13048 +          // `Unused entries shall be set to 'FF FF'.`
 1.13049 +          if ((from != 0xFFFF) && (to != 0xFFFF)) {
 1.13050 +            list.push(from);
 1.13051 +            list.push(to + 1);
 1.13052 +          }
 1.13053 +        }
 1.13054 +      }
 1.13055 +      if (DEBUG) {
 1.13056 +        this.context.debug("CBMIR: " + JSON.stringify(list));
 1.13057 +      }
 1.13058 +
 1.13059 +      Buf.readStringDelimiter(strLength);
 1.13060 +
 1.13061 +      RIL.cellBroadcastConfigs.CBMIR = list;
 1.13062 +      RIL._mergeAllCellBroadcastConfigs();
 1.13063 +    }
 1.13064 +
 1.13065 +    function onerror() {
 1.13066 +      RIL.cellBroadcastConfigs.CBMIR = null;
 1.13067 +      RIL._mergeAllCellBroadcastConfigs();
 1.13068 +    }
 1.13069 +
 1.13070 +    this.context.ICCIOHelper.loadTransparentEF({
 1.13071 +      fileId: ICC_EF_CBMIR,
 1.13072 +      callback: callback.bind(this),
 1.13073 +      onerror: onerror.bind(this)
 1.13074 +    });
 1.13075 +  },
 1.13076 +
 1.13077 +  /**
 1.13078 +   * Read OPL (Operator PLMN List) from (U)SIM.
 1.13079 +   *
 1.13080 +   * See 3GPP TS 31.102 Sec. 4.2.59 for USIM
 1.13081 +   *     3GPP TS 51.011 Sec. 10.3.42 for SIM.
 1.13082 +   */
 1.13083 +  readOPL: function() {
 1.13084 +    let ICCIOHelper = this.context.ICCIOHelper;
 1.13085 +    let opl = [];
 1.13086 +    function callback(options) {
 1.13087 +      let Buf = this.context.Buf;
 1.13088 +      let GsmPDUHelper = this.context.GsmPDUHelper;
 1.13089 +
 1.13090 +      let strLen = Buf.readInt32();
 1.13091 +      // The first 7 bytes are LAI (for UMTS) and the format of LAI is defined
 1.13092 +      // in 3GPP TS 23.003, Sec 4.1
 1.13093 +      //    +-------------+---------+
 1.13094 +      //    | Octet 1 - 3 | MCC/MNC |
 1.13095 +      //    +-------------+---------+
 1.13096 +      //    | Octet 4 - 7 |   LAC   |
 1.13097 +      //    +-------------+---------+
 1.13098 +      let mccMnc = [GsmPDUHelper.readHexOctet(),
 1.13099 +                    GsmPDUHelper.readHexOctet(),
 1.13100 +                    GsmPDUHelper.readHexOctet()];
 1.13101 +      if (mccMnc[0] != 0xFF || mccMnc[1] != 0xFF || mccMnc[2] != 0xFF) {
 1.13102 +        let oplElement = {};
 1.13103 +        let semiOctets = [];
 1.13104 +        for (let i = 0; i < mccMnc.length; i++) {
 1.13105 +          semiOctets.push((mccMnc[i] & 0xf0) >> 4);
 1.13106 +          semiOctets.push(mccMnc[i] & 0x0f);
 1.13107 +        }
 1.13108 +        let reformat = [semiOctets[1], semiOctets[0], semiOctets[3],
 1.13109 +                        semiOctets[5], semiOctets[4], semiOctets[2]];
 1.13110 +        let buf = "";
 1.13111 +        for (let i = 0; i < reformat.length; i++) {
 1.13112 +          if (reformat[i] != 0xF) {
 1.13113 +            buf += GsmPDUHelper.semiOctetToBcdChar(reformat[i]);
 1.13114 +          }
 1.13115 +          if (i === 2) {
 1.13116 +            // 0-2: MCC
 1.13117 +            oplElement.mcc = buf;
 1.13118 +            buf = "";
 1.13119 +          } else if (i === 5) {
 1.13120 +            // 3-5: MNC
 1.13121 +            oplElement.mnc = buf;
 1.13122 +          }
 1.13123 +        }
 1.13124 +        // LAC/TAC
 1.13125 +        oplElement.lacTacStart =
 1.13126 +          (GsmPDUHelper.readHexOctet() << 8) | GsmPDUHelper.readHexOctet();
 1.13127 +        oplElement.lacTacEnd =
 1.13128 +          (GsmPDUHelper.readHexOctet() << 8) | GsmPDUHelper.readHexOctet();
 1.13129 +        // PLMN Network Name Record Identifier
 1.13130 +        oplElement.pnnRecordId = GsmPDUHelper.readHexOctet();
 1.13131 +        if (DEBUG) {
 1.13132 +          this.context.debug("OPL: [" + (opl.length + 1) + "]: " +
 1.13133 +                             JSON.stringify(oplElement));
 1.13134 +        }
 1.13135 +        opl.push(oplElement);
 1.13136 +      } else {
 1.13137 +        Buf.seekIncoming(5 * Buf.PDU_HEX_OCTET_SIZE);
 1.13138 +      }
 1.13139 +      Buf.readStringDelimiter(strLen);
 1.13140 +
 1.13141 +      if (options.p1 < options.totalRecords) {
 1.13142 +        ICCIOHelper.loadNextRecord(options);
 1.13143 +      } else {
 1.13144 +        this.context.RIL.iccInfoPrivate.OPL = opl;
 1.13145 +      }
 1.13146 +    }
 1.13147 +
 1.13148 +    ICCIOHelper.loadLinearFixedEF({fileId: ICC_EF_OPL,
 1.13149 +                                   callback: callback.bind(this)});
 1.13150 +  },
 1.13151 +
 1.13152 +  /**
 1.13153 +   * Read PNN (PLMN Network Name) from (U)SIM.
 1.13154 +   *
 1.13155 +   * See 3GPP TS 31.102 Sec. 4.2.58 for USIM
 1.13156 +   *     3GPP TS 51.011 Sec. 10.3.41 for SIM.
 1.13157 +   */
 1.13158 +  readPNN: function() {
 1.13159 +    let ICCIOHelper = this.context.ICCIOHelper;
 1.13160 +    function callback(options) {
 1.13161 +      let pnnElement;
 1.13162 +      let Buf = this.context.Buf;
 1.13163 +      let strLen = Buf.readInt32();
 1.13164 +      let octetLen = strLen / 2;
 1.13165 +      let readLen = 0;
 1.13166 +
 1.13167 +      let GsmPDUHelper = this.context.GsmPDUHelper;
 1.13168 +      while (readLen < octetLen) {
 1.13169 +        let tlvTag = GsmPDUHelper.readHexOctet();
 1.13170 +
 1.13171 +        if (tlvTag == 0xFF) {
 1.13172 +          // Unused byte
 1.13173 +          readLen++;
 1.13174 +          Buf.seekIncoming((octetLen - readLen) * Buf.PDU_HEX_OCTET_SIZE);
 1.13175 +          break;
 1.13176 +        }
 1.13177 +
 1.13178 +        // Needs this check to avoid initializing twice.
 1.13179 +        pnnElement = pnnElement || {};
 1.13180 +
 1.13181 +        let tlvLen = GsmPDUHelper.readHexOctet();
 1.13182 +
 1.13183 +        switch (tlvTag) {
 1.13184 +          case PNN_IEI_FULL_NETWORK_NAME:
 1.13185 +            pnnElement.fullName = GsmPDUHelper.readNetworkName(tlvLen);
 1.13186 +            break;
 1.13187 +          case PNN_IEI_SHORT_NETWORK_NAME:
 1.13188 +            pnnElement.shortName = GsmPDUHelper.readNetworkName(tlvLen);
 1.13189 +            break;
 1.13190 +          default:
 1.13191 +            Buf.seekIncoming(tlvLen * Buf.PDU_HEX_OCTET_SIZE);
 1.13192 +            break;
 1.13193 +        }
 1.13194 +
 1.13195 +        readLen += (tlvLen + 2); // +2 for tlvTag and tlvLen
 1.13196 +      }
 1.13197 +      Buf.readStringDelimiter(strLen);
 1.13198 +
 1.13199 +      if (pnnElement) {
 1.13200 +        pnn.push(pnnElement);
 1.13201 +      }
 1.13202 +
 1.13203 +      // Will ignore remaining records when got the contents of a record are all 0xff.
 1.13204 +      if (pnnElement && options.p1 < options.totalRecords) {
 1.13205 +        ICCIOHelper.loadNextRecord(options);
 1.13206 +      } else {
 1.13207 +        if (DEBUG) {
 1.13208 +          for (let i = 0; i < pnn.length; i++) {
 1.13209 +            this.context.debug("PNN: [" + i + "]: " + JSON.stringify(pnn[i]));
 1.13210 +          }
 1.13211 +        }
 1.13212 +        this.context.RIL.iccInfoPrivate.PNN = pnn;
 1.13213 +      }
 1.13214 +    }
 1.13215 +
 1.13216 +    let pnn = [];
 1.13217 +    ICCIOHelper.loadLinearFixedEF({fileId: ICC_EF_PNN,
 1.13218 +                                   callback: callback.bind(this)});
 1.13219 +  },
 1.13220 +
 1.13221 +  /**
 1.13222 +   *  Read the list of PLMN (Public Land Mobile Network) entries
 1.13223 +   *  We cannot directly rely on readSwappedNibbleBcdToString(),
 1.13224 +   *  since it will no correctly handle some corner-cases that are
 1.13225 +   *  not a problem in our case (0xFF 0xFF 0xFF).
 1.13226 +   *
 1.13227 +   *  @param length The number of PLMN records.
 1.13228 +   *  @return An array of string corresponding to the PLMNs.
 1.13229 +   */
 1.13230 +  readPLMNEntries: function(length) {
 1.13231 +    let plmnList = [];
 1.13232 +    // Each PLMN entry has 3 bytes.
 1.13233 +    if (DEBUG) {
 1.13234 +      this.context.debug("PLMN entries length = " + length);
 1.13235 +    }
 1.13236 +    let GsmPDUHelper = this.context.GsmPDUHelper;
 1.13237 +    let index = 0;
 1.13238 +    while (index < length) {
 1.13239 +      // Unused entries will be 0xFFFFFF, according to EF_SPDI
 1.13240 +      // specs (TS 131 102, section 4.2.66)
 1.13241 +      try {
 1.13242 +        let plmn = [GsmPDUHelper.readHexOctet(),
 1.13243 +                    GsmPDUHelper.readHexOctet(),
 1.13244 +                    GsmPDUHelper.readHexOctet()];
 1.13245 +        if (DEBUG) {
 1.13246 +          this.context.debug("Reading PLMN entry: [" + index + "]: '" + plmn + "'");
 1.13247 +        }
 1.13248 +        if (plmn[0] != 0xFF &&
 1.13249 +            plmn[1] != 0xFF &&
 1.13250 +            plmn[2] != 0xFF) {
 1.13251 +          let semiOctets = [];
 1.13252 +          for (let idx = 0; idx < plmn.length; idx++) {
 1.13253 +            semiOctets.push((plmn[idx] & 0xF0) >> 4);
 1.13254 +            semiOctets.push(plmn[idx] & 0x0F);
 1.13255 +          }
 1.13256 +
 1.13257 +          // According to TS 24.301, 9.9.3.12, the semi octets is arranged
 1.13258 +          // in format:
 1.13259 +          // Byte 1: MCC[2] | MCC[1]
 1.13260 +          // Byte 2: MNC[3] | MCC[3]
 1.13261 +          // Byte 3: MNC[2] | MNC[1]
 1.13262 +          // Therefore, we need to rearrange them.
 1.13263 +          let reformat = [semiOctets[1], semiOctets[0], semiOctets[3],
 1.13264 +                          semiOctets[5], semiOctets[4], semiOctets[2]];
 1.13265 +          let buf = "";
 1.13266 +          let plmnEntry = {};
 1.13267 +          for (let i = 0; i < reformat.length; i++) {
 1.13268 +            if (reformat[i] != 0xF) {
 1.13269 +              buf += GsmPDUHelper.semiOctetToBcdChar(reformat[i]);
 1.13270 +            }
 1.13271 +            if (i === 2) {
 1.13272 +              // 0-2: MCC
 1.13273 +              plmnEntry.mcc = buf;
 1.13274 +              buf = "";
 1.13275 +            } else if (i === 5) {
 1.13276 +              // 3-5: MNC
 1.13277 +              plmnEntry.mnc = buf;
 1.13278 +            }
 1.13279 +          }
 1.13280 +          if (DEBUG) {
 1.13281 +            this.context.debug("PLMN = " + plmnEntry.mcc + ", " + plmnEntry.mnc);
 1.13282 +          }
 1.13283 +          plmnList.push(plmnEntry);
 1.13284 +        }
 1.13285 +      } catch (e) {
 1.13286 +        if (DEBUG) {
 1.13287 +          this.context.debug("PLMN entry " + index + " is invalid.");
 1.13288 +        }
 1.13289 +        break;
 1.13290 +      }
 1.13291 +      index ++;
 1.13292 +    }
 1.13293 +    return plmnList;
 1.13294 +  },
 1.13295 +
 1.13296 +  /**
 1.13297 +   * Read the SMS from the ICC.
 1.13298 +   *
 1.13299 +   * @param recordNumber The number of the record shall be loaded.
 1.13300 +   * @param onsuccess    Callback to be called when success.
 1.13301 +   * @param onerror      Callback to be called when error.
 1.13302 +   */
 1.13303 +  readSMS: function(recordNumber, onsuccess, onerror) {
 1.13304 +    function callback(options) {
 1.13305 +      let Buf = this.context.Buf;
 1.13306 +      let strLen = Buf.readInt32();
 1.13307 +
 1.13308 +      // TS 51.011, 10.5.3 EF_SMS
 1.13309 +      // b3 b2 b1
 1.13310 +      //  0  0  1 message received by MS from network; message read
 1.13311 +      //  0  1  1 message received by MS from network; message to be read
 1.13312 +      //  1  1  1 MS originating message; message to be sent
 1.13313 +      //  1  0  1 MS originating message; message sent to the network:
 1.13314 +      let GsmPDUHelper = this.context.GsmPDUHelper;
 1.13315 +      let status = GsmPDUHelper.readHexOctet();
 1.13316 +
 1.13317 +      let message = GsmPDUHelper.readMessage();
 1.13318 +      message.simStatus = status;
 1.13319 +
 1.13320 +      // Consumes the remaining buffer
 1.13321 +      Buf.seekIncoming(Buf.getReadAvailable() - Buf.PDU_HEX_OCTET_SIZE);
 1.13322 +
 1.13323 +      Buf.readStringDelimiter(strLen);
 1.13324 +
 1.13325 +      if (message) {
 1.13326 +        onsuccess(message);
 1.13327 +      } else {
 1.13328 +        onerror("Failed to decode SMS on SIM #" + recordNumber);
 1.13329 +      }
 1.13330 +    }
 1.13331 +
 1.13332 +    this.context.ICCIOHelper.loadLinearFixedEF({
 1.13333 +      fileId: ICC_EF_SMS,
 1.13334 +      recordNumber: recordNumber,
 1.13335 +      callback: callback.bind(this),
 1.13336 +      onerror: onerror
 1.13337 +    });
 1.13338 +  },
 1.13339 +};
 1.13340 +
 1.13341 +function RuimRecordHelperObject(aContext) {
 1.13342 +  this.context = aContext;
 1.13343 +}
 1.13344 +RuimRecordHelperObject.prototype = {
 1.13345 +  context: null,
 1.13346 +
 1.13347 +  fetchRuimRecords: function() {
 1.13348 +    this.getIMSI_M();
 1.13349 +    this.readCST();
 1.13350 +    this.readCDMAHome();
 1.13351 +    this.context.RIL.getCdmaSubscription();
 1.13352 +  },
 1.13353 +
 1.13354 +  /**
 1.13355 +   * Get IMSI_M from CSIM/RUIM.
 1.13356 +   * See 3GPP2 C.S0065 Sec. 5.2.2
 1.13357 +   */
 1.13358 +  getIMSI_M: function() {
 1.13359 +    function callback() {
 1.13360 +      let Buf = this.context.Buf;
 1.13361 +      let strLen = Buf.readInt32();
 1.13362 +      let encodedImsi = this.context.GsmPDUHelper.readHexOctetArray(strLen / 2);
 1.13363 +      Buf.readStringDelimiter(strLen);
 1.13364 +
 1.13365 +      if ((encodedImsi[CSIM_IMSI_M_PROGRAMMED_BYTE] & 0x80)) { // IMSI_M programmed
 1.13366 +        let RIL = this.context.RIL;
 1.13367 +        RIL.iccInfoPrivate.imsi = this.decodeIMSI(encodedImsi);
 1.13368 +        RIL.sendChromeMessage({rilMessageType: "iccimsi",
 1.13369 +                               imsi: RIL.iccInfoPrivate.imsi});
 1.13370 +
 1.13371 +        let ICCUtilsHelper = this.context.ICCUtilsHelper;
 1.13372 +        let mccMnc = ICCUtilsHelper.parseMccMncFromImsi(RIL.iccInfoPrivate.imsi);
 1.13373 +        if (mccMnc) {
 1.13374 +          RIL.iccInfo.mcc = mccMnc.mcc;
 1.13375 +          RIL.iccInfo.mnc = mccMnc.mnc;
 1.13376 +          ICCUtilsHelper.handleICCInfoChange();
 1.13377 +        }
 1.13378 +      }
 1.13379 +    }
 1.13380 +
 1.13381 +    this.context.ICCIOHelper.loadTransparentEF({
 1.13382 +      fileId: ICC_EF_CSIM_IMSI_M,
 1.13383 +      callback: callback.bind(this)
 1.13384 +    });
 1.13385 +  },
 1.13386 +
 1.13387 +  /**
 1.13388 +   * Decode IMSI from IMSI_M
 1.13389 +   * See 3GPP2 C.S0005 Sec. 2.3.1
 1.13390 +   * +---+---------+------------+---+--------+---------+---+---------+--------+
 1.13391 +   * |RFU|   MCC   | programmed |RFU|  MNC   |  MIN1   |RFU|   MIN2  |  CLASS |
 1.13392 +   * +---+---------+------------+---+--------+---------+---+---------+--------+
 1.13393 +   * | 6 | 10 bits |   8 bits   | 1 | 7 bits | 24 bits | 6 | 10 bits | 8 bits |
 1.13394 +   * +---+---------+------------+---+--------+---------+---+---------+--------+
 1.13395 +   */
 1.13396 +  decodeIMSI: function(encodedImsi) {
 1.13397 +    // MCC: 10 bits, 3 digits
 1.13398 +    let encodedMCC = ((encodedImsi[CSIM_IMSI_M_MCC_BYTE + 1] & 0x03) << 8) +
 1.13399 +                      (encodedImsi[CSIM_IMSI_M_MCC_BYTE] & 0xff);
 1.13400 +    let mcc = this.decodeIMSIValue(encodedMCC, 3);
 1.13401 +
 1.13402 +    // MNC: 7 bits, 2 digits
 1.13403 +    let encodedMNC =  encodedImsi[CSIM_IMSI_M_MNC_BYTE] & 0x7f;
 1.13404 +    let mnc = this.decodeIMSIValue(encodedMNC, 2);
 1.13405 +
 1.13406 +    // MIN2: 10 bits, 3 digits
 1.13407 +    let encodedMIN2 = ((encodedImsi[CSIM_IMSI_M_MIN2_BYTE + 1] & 0x03) << 8) +
 1.13408 +                       (encodedImsi[CSIM_IMSI_M_MIN2_BYTE] & 0xff);
 1.13409 +    let min2 = this.decodeIMSIValue(encodedMIN2, 3);
 1.13410 +
 1.13411 +    // MIN1: 10+4+10 bits, 3+1+3 digits
 1.13412 +    let encodedMIN1First3 = ((encodedImsi[CSIM_IMSI_M_MIN1_BYTE + 2] & 0xff) << 2) +
 1.13413 +                             ((encodedImsi[CSIM_IMSI_M_MIN1_BYTE + 1] & 0xc0) >> 6);
 1.13414 +    let min1First3 = this.decodeIMSIValue(encodedMIN1First3, 3);
 1.13415 +
 1.13416 +    let encodedFourthDigit = (encodedImsi[CSIM_IMSI_M_MIN1_BYTE + 1] & 0x3c) >> 2;
 1.13417 +    if (encodedFourthDigit > 9) {
 1.13418 +      encodedFourthDigit = 0;
 1.13419 +    }
 1.13420 +    let fourthDigit = encodedFourthDigit.toString();
 1.13421 +
 1.13422 +    let encodedMIN1Last3 = ((encodedImsi[CSIM_IMSI_M_MIN1_BYTE + 1] & 0x03) << 8) +
 1.13423 +                            (encodedImsi[CSIM_IMSI_M_MIN1_BYTE] & 0xff);
 1.13424 +    let min1Last3 = this.decodeIMSIValue(encodedMIN1Last3, 3);
 1.13425 +
 1.13426 +    return mcc + mnc + min2 + min1First3 + fourthDigit + min1Last3;
 1.13427 +  },
 1.13428 +
 1.13429 +  /**
 1.13430 +   * Decode IMSI Helper function
 1.13431 +   * See 3GPP2 C.S0005 section 2.3.1.1
 1.13432 +   */
 1.13433 +  decodeIMSIValue: function(encoded, length) {
 1.13434 +    let offset = length === 3 ? 111 : 11;
 1.13435 +    let value = encoded + offset;
 1.13436 +
 1.13437 +    for (let base = 10, temp = value, i = 0; i < length; i++) {
 1.13438 +      if (temp % 10 === 0) {
 1.13439 +        value -= base;
 1.13440 +      }
 1.13441 +      temp = Math.floor(value / base);
 1.13442 +      base = base * 10;
 1.13443 +    }
 1.13444 +
 1.13445 +    let s = value.toString();
 1.13446 +    while (s.length < length) {
 1.13447 +      s = "0" + s;
 1.13448 +    }
 1.13449 +
 1.13450 +    return s;
 1.13451 +  },
 1.13452 +
 1.13453 +  /**
 1.13454 +   * Read CDMAHOME for CSIM.
 1.13455 +   * See 3GPP2 C.S0023 Sec. 3.4.8.
 1.13456 +   */
 1.13457 +  readCDMAHome: function() {
 1.13458 +    let ICCIOHelper = this.context.ICCIOHelper;
 1.13459 +
 1.13460 +    function callback(options) {
 1.13461 +      let Buf = this.context.Buf;
 1.13462 +      let GsmPDUHelper = this.context.GsmPDUHelper;
 1.13463 +
 1.13464 +      let strLen = Buf.readInt32();
 1.13465 +      let tempOctet = GsmPDUHelper.readHexOctet();
 1.13466 +      cdmaHomeSystemId.push(((GsmPDUHelper.readHexOctet() & 0x7f) << 8) | tempOctet);
 1.13467 +      tempOctet = GsmPDUHelper.readHexOctet();
 1.13468 +      cdmaHomeNetworkId.push(((GsmPDUHelper.readHexOctet() & 0xff) << 8) | tempOctet);
 1.13469 +
 1.13470 +      // Consuming the last octet: band class.
 1.13471 +      Buf.seekIncoming(Buf.PDU_HEX_OCTET_SIZE);
 1.13472 +
 1.13473 +      Buf.readStringDelimiter(strLen);
 1.13474 +      if (options.p1 < options.totalRecords) {
 1.13475 +        ICCIOHelper.loadNextRecord(options);
 1.13476 +      } else {
 1.13477 +        if (DEBUG) {
 1.13478 +          this.context.debug("CDMAHome system id: " +
 1.13479 +                             JSON.stringify(cdmaHomeSystemId));
 1.13480 +          this.context.debug("CDMAHome network id: " +
 1.13481 +                             JSON.stringify(cdmaHomeNetworkId));
 1.13482 +        }
 1.13483 +        this.context.RIL.cdmaHome = {
 1.13484 +          systemId: cdmaHomeSystemId,
 1.13485 +          networkId: cdmaHomeNetworkId
 1.13486 +        };
 1.13487 +      }
 1.13488 +    }
 1.13489 +
 1.13490 +    let cdmaHomeSystemId = [], cdmaHomeNetworkId = [];
 1.13491 +    ICCIOHelper.loadLinearFixedEF({fileId: ICC_EF_CSIM_CDMAHOME,
 1.13492 +                                   callback: callback.bind(this)});
 1.13493 +  },
 1.13494 +
 1.13495 +  /**
 1.13496 +   * Read CDMA Service Table.
 1.13497 +   * See 3GPP2 C.S0023 Sec. 3.4.18
 1.13498 +   */
 1.13499 +  readCST: function() {
 1.13500 +    function callback() {
 1.13501 +      let Buf = this.context.Buf;
 1.13502 +      let RIL = this.context.RIL;
 1.13503 +
 1.13504 +      let strLen = Buf.readInt32();
 1.13505 +      // Each octet is encoded into two chars.
 1.13506 +      RIL.iccInfoPrivate.cst =
 1.13507 +        this.context.GsmPDUHelper.readHexOctetArray(strLen / 2);
 1.13508 +      Buf.readStringDelimiter(strLen);
 1.13509 +
 1.13510 +      if (DEBUG) {
 1.13511 +        let str = "";
 1.13512 +        for (let i = 0; i < RIL.iccInfoPrivate.cst.length; i++) {
 1.13513 +          str += RIL.iccInfoPrivate.cst[i] + ", ";
 1.13514 +        }
 1.13515 +        this.context.debug("CST: " + str);
 1.13516 +      }
 1.13517 +
 1.13518 +      if (this.context.ICCUtilsHelper.isICCServiceAvailable("SPN")) {
 1.13519 +        if (DEBUG) this.context.debug("SPN: SPN is available");
 1.13520 +        this.readSPN();
 1.13521 +      }
 1.13522 +    }
 1.13523 +    this.context.ICCIOHelper.loadTransparentEF({
 1.13524 +      fileId: ICC_EF_CSIM_CST,
 1.13525 +      callback: callback.bind(this)
 1.13526 +    });
 1.13527 +  },
 1.13528 +
 1.13529 +  readSPN: function() {
 1.13530 +    function callback() {
 1.13531 +      let Buf = this.context.Buf;
 1.13532 +      let strLen = Buf.readInt32();
 1.13533 +      let octetLen = strLen / 2;
 1.13534 +
 1.13535 +      let GsmPDUHelper = this.context.GsmPDUHelper;
 1.13536 +      let displayCondition = GsmPDUHelper.readHexOctet();
 1.13537 +      let codingScheme = GsmPDUHelper.readHexOctet();
 1.13538 +      // Skip one octet: language indicator.
 1.13539 +      Buf.seekIncoming(Buf.PDU_HEX_OCTET_SIZE);
 1.13540 +      let readLen = 3;
 1.13541 +
 1.13542 +      // SPN String ends up with 0xff.
 1.13543 +      let userDataBuffer = [];
 1.13544 +
 1.13545 +      while (readLen < octetLen) {
 1.13546 +        let octet = GsmPDUHelper.readHexOctet();
 1.13547 +        readLen++;
 1.13548 +        if (octet == 0xff) {
 1.13549 +          break;
 1.13550 +        }
 1.13551 +        userDataBuffer.push(octet);
 1.13552 +      }
 1.13553 +
 1.13554 +      this.context.BitBufferHelper.startRead(userDataBuffer);
 1.13555 +
 1.13556 +      let CdmaPDUHelper = this.context.CdmaPDUHelper;
 1.13557 +      let msgLen;
 1.13558 +      switch (CdmaPDUHelper.getCdmaMsgEncoding(codingScheme)) {
 1.13559 +      case PDU_DCS_MSG_CODING_7BITS_ALPHABET:
 1.13560 +        msgLen = Math.floor(userDataBuffer.length * 8 / 7);
 1.13561 +        break;
 1.13562 +      case PDU_DCS_MSG_CODING_8BITS_ALPHABET:
 1.13563 +        msgLen = userDataBuffer.length;
 1.13564 +        break;
 1.13565 +      case PDU_DCS_MSG_CODING_16BITS_ALPHABET:
 1.13566 +        msgLen = Math.floor(userDataBuffer.length / 2);
 1.13567 +        break;
 1.13568 +      }
 1.13569 +
 1.13570 +      let RIL = this.context.RIL;
 1.13571 +      RIL.iccInfo.spn = CdmaPDUHelper.decodeCdmaPDUMsg(codingScheme, null, msgLen);
 1.13572 +      if (DEBUG) {
 1.13573 +        this.context.debug("CDMA SPN: " + RIL.iccInfo.spn +
 1.13574 +                           ", Display condition: " + displayCondition);
 1.13575 +      }
 1.13576 +      RIL.iccInfoPrivate.spnDisplayCondition = displayCondition;
 1.13577 +      Buf.seekIncoming((octetLen - readLen) * Buf.PDU_HEX_OCTET_SIZE);
 1.13578 +      Buf.readStringDelimiter(strLen);
 1.13579 +    }
 1.13580 +
 1.13581 +    this.context.ICCIOHelper.loadTransparentEF({
 1.13582 +      fileId: ICC_EF_CSIM_SPN,
 1.13583 +      callback: callback.bind(this)
 1.13584 +    });
 1.13585 +  }
 1.13586 +};
 1.13587 +
 1.13588 +/**
 1.13589 + * Helper functions for ICC utilities.
 1.13590 + */
 1.13591 +function ICCUtilsHelperObject(aContext) {
 1.13592 +  this.context = aContext;
 1.13593 +}
 1.13594 +ICCUtilsHelperObject.prototype = {
 1.13595 +  context: null,
 1.13596 +
 1.13597 +  /**
 1.13598 +   * Get network names by using EF_OPL and EF_PNN
 1.13599 +   *
 1.13600 +   * @See 3GPP TS 31.102 sec. 4.2.58 and sec. 4.2.59 for USIM,
 1.13601 +   *      3GPP TS 51.011 sec. 10.3.41 and sec. 10.3.42 for SIM.
 1.13602 +   *
 1.13603 +   * @param mcc   The mobile country code of the network.
 1.13604 +   * @param mnc   The mobile network code of the network.
 1.13605 +   * @param lac   The location area code of the network.
 1.13606 +   */
 1.13607 +  getNetworkNameFromICC: function(mcc, mnc, lac) {
 1.13608 +    let RIL = this.context.RIL;
 1.13609 +    let iccInfoPriv = RIL.iccInfoPrivate;
 1.13610 +    let iccInfo = RIL.iccInfo;
 1.13611 +    let pnnEntry;
 1.13612 +
 1.13613 +    if (!mcc || !mnc || !lac) {
 1.13614 +      return null;
 1.13615 +    }
 1.13616 +
 1.13617 +    // We won't get network name if there is no PNN file.
 1.13618 +    if (!iccInfoPriv.PNN) {
 1.13619 +      return null;
 1.13620 +    }
 1.13621 +
 1.13622 +    if (!iccInfoPriv.OPL) {
 1.13623 +      // When OPL is not present:
 1.13624 +      // According to 3GPP TS 31.102 Sec. 4.2.58 and 3GPP TS 51.011 Sec. 10.3.41,
 1.13625 +      // If EF_OPL is not present, the first record in this EF is used for the
 1.13626 +      // default network name when registered to the HPLMN.
 1.13627 +      // If we haven't get pnnEntry assigned, we should try to assign default
 1.13628 +      // value to it.
 1.13629 +      if (mcc == iccInfo.mcc && mnc == iccInfo.mnc) {
 1.13630 +        pnnEntry = iccInfoPriv.PNN[0];
 1.13631 +      }
 1.13632 +    } else {
 1.13633 +      // According to 3GPP TS 31.102 Sec. 4.2.59 and 3GPP TS 51.011 Sec. 10.3.42,
 1.13634 +      // the ME shall use this EF_OPL in association with the EF_PNN in place
 1.13635 +      // of any network name stored within the ME's internal list and any network
 1.13636 +      // name received when registered to the PLMN.
 1.13637 +      let length = iccInfoPriv.OPL ? iccInfoPriv.OPL.length : 0;
 1.13638 +      for (let i = 0; i < length; i++) {
 1.13639 +        let opl = iccInfoPriv.OPL[i];
 1.13640 +        // Try to match the MCC/MNC.
 1.13641 +        if (mcc != opl.mcc || mnc != opl.mnc) {
 1.13642 +          continue;
 1.13643 +        }
 1.13644 +        // Try to match the location area code. If current local area code is
 1.13645 +        // covered by lac range that specified in the OPL entry, use the PNN
 1.13646 +        // that specified in the OPL entry.
 1.13647 +        if ((opl.lacTacStart === 0x0 && opl.lacTacEnd == 0xFFFE) ||
 1.13648 +            (opl.lacTacStart <= lac && opl.lacTacEnd >= lac)) {
 1.13649 +          if (opl.pnnRecordId === 0) {
 1.13650 +            // See 3GPP TS 31.102 Sec. 4.2.59 and 3GPP TS 51.011 Sec. 10.3.42,
 1.13651 +            // A value of '00' indicates that the name is to be taken from other
 1.13652 +            // sources.
 1.13653 +            return null;
 1.13654 +          }
 1.13655 +          pnnEntry = iccInfoPriv.PNN[opl.pnnRecordId - 1];
 1.13656 +          break;
 1.13657 +        }
 1.13658 +      }
 1.13659 +    }
 1.13660 +
 1.13661 +    if (!pnnEntry) {
 1.13662 +      return null;
 1.13663 +    }
 1.13664 +
 1.13665 +    // Return a new object to avoid global variable, PNN, be modified by accident.
 1.13666 +    return { fullName: pnnEntry.fullName || "",
 1.13667 +             shortName: pnnEntry.shortName || "" };
 1.13668 +  },
 1.13669 +
 1.13670 +  /**
 1.13671 +   * This will compute the spnDisplay field of the network.
 1.13672 +   * See TS 22.101 Annex A and TS 51.011 10.3.11 for details.
 1.13673 +   *
 1.13674 +   * @return True if some of iccInfo is changed in by this function.
 1.13675 +   */
 1.13676 +  updateDisplayCondition: function() {
 1.13677 +    let RIL = this.context.RIL;
 1.13678 +
 1.13679 +    // If EFspn isn't existed in SIM or it haven't been read yet, we should
 1.13680 +    // just set isDisplayNetworkNameRequired = true and
 1.13681 +    // isDisplaySpnRequired = false
 1.13682 +    let iccInfo = RIL.iccInfo;
 1.13683 +    let iccInfoPriv = RIL.iccInfoPrivate;
 1.13684 +    let displayCondition = iccInfoPriv.spnDisplayCondition;
 1.13685 +    let origIsDisplayNetworkNameRequired = iccInfo.isDisplayNetworkNameRequired;
 1.13686 +    let origIsDisplaySPNRequired = iccInfo.isDisplaySpnRequired;
 1.13687 +
 1.13688 +    if (displayCondition === undefined) {
 1.13689 +      iccInfo.isDisplayNetworkNameRequired = true;
 1.13690 +      iccInfo.isDisplaySpnRequired = false;
 1.13691 +    } else if (RIL._isCdma) {
 1.13692 +      // CDMA family display rule.
 1.13693 +      let cdmaHome = RIL.cdmaHome;
 1.13694 +      let cell = RIL.voiceRegistrationState.cell;
 1.13695 +      let sid = cell && cell.cdmaSystemId;
 1.13696 +      let nid = cell && cell.cdmaNetworkId;
 1.13697 +
 1.13698 +      iccInfo.isDisplayNetworkNameRequired = false;
 1.13699 +
 1.13700 +      // If display condition is 0x0, we don't even need to check network id
 1.13701 +      // or system id.
 1.13702 +      if (displayCondition === 0x0) {
 1.13703 +        iccInfo.isDisplaySpnRequired = false;
 1.13704 +      } else {
 1.13705 +        // CDMA SPN Display condition dosen't specify whenever network name is
 1.13706 +        // reqired.
 1.13707 +        if (!cdmaHome ||
 1.13708 +            !cdmaHome.systemId ||
 1.13709 +            cdmaHome.systemId.length === 0 ||
 1.13710 +            cdmaHome.systemId.length != cdmaHome.networkId.length ||
 1.13711 +            !sid || !nid) {
 1.13712 +          // CDMA Home haven't been ready, or we haven't got the system id and
 1.13713 +          // network id of the network we register to, assuming we are in home
 1.13714 +          // network.
 1.13715 +          iccInfo.isDisplaySpnRequired = true;
 1.13716 +        } else {
 1.13717 +          // Determine if we are registered in the home service area.
 1.13718 +          // System ID and Network ID are described in 3GPP2 C.S0005 Sec. 2.6.5.2.
 1.13719 +          let inHomeArea = false;
 1.13720 +          for (let i = 0; i < cdmaHome.systemId.length; i++) {
 1.13721 +            let homeSid = cdmaHome.systemId[i],
 1.13722 +                homeNid = cdmaHome.networkId[i];
 1.13723 +            if (homeSid === 0 || homeNid === 0 // Reserved system id/network id
 1.13724 +               || homeSid != sid) {
 1.13725 +              continue;
 1.13726 +            }
 1.13727 +            // According to 3GPP2 C.S0005 Sec. 2.6.5.2, NID number 65535 means
 1.13728 +            // all networks in the system should be considered as home.
 1.13729 +            if (homeNid == 65535 || homeNid == nid) {
 1.13730 +              inHomeArea = true;
 1.13731 +              break;
 1.13732 +            }
 1.13733 +          }
 1.13734 +          iccInfo.isDisplaySpnRequired = inHomeArea;
 1.13735 +        }
 1.13736 +      }
 1.13737 +    } else {
 1.13738 +      // GSM family display rule.
 1.13739 +      let operatorMnc = RIL.operator.mnc;
 1.13740 +      let operatorMcc = RIL.operator.mcc;
 1.13741 +
 1.13742 +      // First detect if we are on HPLMN or one of the PLMN
 1.13743 +      // specified by the SIM card.
 1.13744 +      let isOnMatchingPlmn = false;
 1.13745 +
 1.13746 +      // If the current network is the one defined as mcc/mnc
 1.13747 +      // in SIM card, it's okay.
 1.13748 +      if (iccInfo.mcc == operatorMcc && iccInfo.mnc == operatorMnc) {
 1.13749 +        isOnMatchingPlmn = true;
 1.13750 +      }
 1.13751 +
 1.13752 +      // Test to see if operator's mcc/mnc match mcc/mnc of PLMN.
 1.13753 +      if (!isOnMatchingPlmn && iccInfoPriv.SPDI) {
 1.13754 +        let iccSpdi = iccInfoPriv.SPDI; // PLMN list
 1.13755 +        for (let plmn in iccSpdi) {
 1.13756 +          let plmnMcc = iccSpdi[plmn].mcc;
 1.13757 +          let plmnMnc = iccSpdi[plmn].mnc;
 1.13758 +          isOnMatchingPlmn = (plmnMcc == operatorMcc) && (plmnMnc == operatorMnc);
 1.13759 +          if (isOnMatchingPlmn) {
 1.13760 +            break;
 1.13761 +          }
 1.13762 +        }
 1.13763 +      }
 1.13764 +
 1.13765 +      if (isOnMatchingPlmn) {
 1.13766 +        // The first bit of display condition tells us if we should display
 1.13767 +        // registered PLMN.
 1.13768 +        if (DEBUG) {
 1.13769 +          this.context.debug("PLMN is HPLMN or PLMN " + "is in PLMN list");
 1.13770 +        }
 1.13771 +
 1.13772 +        // TS 31.102 Sec. 4.2.66 and TS 51.011 Sec. 10.3.50
 1.13773 +        // EF_SPDI contains a list of PLMNs in which the Service Provider Name
 1.13774 +        // shall be displayed.
 1.13775 +        iccInfo.isDisplaySpnRequired = true;
 1.13776 +        iccInfo.isDisplayNetworkNameRequired = (displayCondition & 0x01) !== 0;
 1.13777 +      } else {
 1.13778 +        // The second bit of display condition tells us if we should display
 1.13779 +        // registered PLMN.
 1.13780 +        if (DEBUG) {
 1.13781 +          this.context.debug("PLMN isn't HPLMN and PLMN isn't in PLMN list");
 1.13782 +        }
 1.13783 +
 1.13784 +        // We didn't found the requirement of displaying network name if
 1.13785 +        // current PLMN isn't HPLMN nor one of PLMN in SPDI. So we keep
 1.13786 +        // isDisplayNetworkNameRequired false.
 1.13787 +        iccInfo.isDisplayNetworkNameRequired = false;
 1.13788 +        iccInfo.isDisplaySpnRequired = (displayCondition & 0x02) === 0;
 1.13789 +      }
 1.13790 +    }
 1.13791 +
 1.13792 +    if (DEBUG) {
 1.13793 +      this.context.debug("isDisplayNetworkNameRequired = " +
 1.13794 +                         iccInfo.isDisplayNetworkNameRequired);
 1.13795 +      this.context.debug("isDisplaySpnRequired = " + iccInfo.isDisplaySpnRequired);
 1.13796 +    }
 1.13797 +
 1.13798 +    return ((origIsDisplayNetworkNameRequired !== iccInfo.isDisplayNetworkNameRequired) ||
 1.13799 +            (origIsDisplaySPNRequired !== iccInfo.isDisplaySpnRequired));
 1.13800 +  },
 1.13801 +
 1.13802 +  decodeSimTlvs: function(tlvsLen) {
 1.13803 +    let GsmPDUHelper = this.context.GsmPDUHelper;
 1.13804 +
 1.13805 +    let index = 0;
 1.13806 +    let tlvs = [];
 1.13807 +    while (index < tlvsLen) {
 1.13808 +      let simTlv = {
 1.13809 +        tag : GsmPDUHelper.readHexOctet(),
 1.13810 +        length : GsmPDUHelper.readHexOctet(),
 1.13811 +      };
 1.13812 +      simTlv.value = GsmPDUHelper.readHexOctetArray(simTlv.length);
 1.13813 +      tlvs.push(simTlv);
 1.13814 +      index += simTlv.length + 2; // The length of 'tag' and 'length' field.
 1.13815 +    }
 1.13816 +    return tlvs;
 1.13817 +  },
 1.13818 +
 1.13819 +  /**
 1.13820 +   * Parse those TLVs and convert it to an object.
 1.13821 +   */
 1.13822 +  parsePbrTlvs: function(pbrTlvs) {
 1.13823 +    let pbr = {};
 1.13824 +    for (let i = 0; i < pbrTlvs.length; i++) {
 1.13825 +      let pbrTlv = pbrTlvs[i];
 1.13826 +      let anrIndex = 0;
 1.13827 +      for (let j = 0; j < pbrTlv.value.length; j++) {
 1.13828 +        let tlv = pbrTlv.value[j];
 1.13829 +        let tagName = USIM_TAG_NAME[tlv.tag];
 1.13830 +
 1.13831 +        // ANR could have multiple files. We save it as anr0, anr1,...etc.
 1.13832 +        if (tlv.tag == ICC_USIM_EFANR_TAG) {
 1.13833 +          tagName += anrIndex;
 1.13834 +          anrIndex++;
 1.13835 +        }
 1.13836 +        pbr[tagName] = tlv;
 1.13837 +        pbr[tagName].fileType = pbrTlv.tag;
 1.13838 +        pbr[tagName].fileId = (tlv.value[0] << 8) | tlv.value[1];
 1.13839 +        pbr[tagName].sfi = tlv.value[2];
 1.13840 +
 1.13841 +        // For Type 2, the order of files is in the same order in IAP.
 1.13842 +        if (pbrTlv.tag == ICC_USIM_TYPE2_TAG) {
 1.13843 +          pbr[tagName].indexInIAP = j;
 1.13844 +        }
 1.13845 +      }
 1.13846 +    }
 1.13847 +
 1.13848 +    return pbr;
 1.13849 +  },
 1.13850 +
 1.13851 +  /**
 1.13852 +   * Update the ICC information to RadioInterfaceLayer.
 1.13853 +   */
 1.13854 +  handleICCInfoChange: function() {
 1.13855 +    let RIL = this.context.RIL;
 1.13856 +    RIL.iccInfo.rilMessageType = "iccinfochange";
 1.13857 +    RIL.sendChromeMessage(RIL.iccInfo);
 1.13858 +  },
 1.13859 +
 1.13860 +  /**
 1.13861 +   * Get whether specificed (U)SIM service is available.
 1.13862 +   *
 1.13863 +   * @param geckoService
 1.13864 +   *        Service name like "ADN", "BDN", etc.
 1.13865 +   *
 1.13866 +   * @return true if the service is enabled, false otherwise.
 1.13867 +   */
 1.13868 +  isICCServiceAvailable: function(geckoService) {
 1.13869 +    let RIL = this.context.RIL;
 1.13870 +    let serviceTable = RIL._isCdma ? RIL.iccInfoPrivate.cst:
 1.13871 +                                     RIL.iccInfoPrivate.sst;
 1.13872 +    let index, bitmask;
 1.13873 +    if (RIL.appType == CARD_APPTYPE_SIM || RIL.appType == CARD_APPTYPE_RUIM) {
 1.13874 +      /**
 1.13875 +       * Service id is valid in 1..N, and 2 bits are used to code each service.
 1.13876 +       *
 1.13877 +       * +----+--  --+----+----+
 1.13878 +       * | b8 | ...  | b2 | b1 |
 1.13879 +       * +----+--  --+----+----+
 1.13880 +       *
 1.13881 +       * b1 = 0, service not allocated.
 1.13882 +       *      1, service allocated.
 1.13883 +       * b2 = 0, service not activatd.
 1.13884 +       *      1, service allocated.
 1.13885 +       *
 1.13886 +       * @see 3GPP TS 51.011 10.3.7.
 1.13887 +       */
 1.13888 +      let simService;
 1.13889 +      if (RIL.appType == CARD_APPTYPE_SIM) {
 1.13890 +        simService = GECKO_ICC_SERVICES.sim[geckoService];
 1.13891 +      } else {
 1.13892 +        simService = GECKO_ICC_SERVICES.ruim[geckoService];
 1.13893 +      }
 1.13894 +      if (!simService) {
 1.13895 +        return false;
 1.13896 +      }
 1.13897 +      simService -= 1;
 1.13898 +      index = Math.floor(simService / 4);
 1.13899 +      bitmask = 2 << ((simService % 4) << 1);
 1.13900 +    } else if (RIL.appType == CARD_APPTYPE_USIM) {
 1.13901 +      /**
 1.13902 +       * Service id is valid in 1..N, and 1 bit is used to code each service.
 1.13903 +       *
 1.13904 +       * +----+--  --+----+----+
 1.13905 +       * | b8 | ...  | b2 | b1 |
 1.13906 +       * +----+--  --+----+----+
 1.13907 +       *
 1.13908 +       * b1 = 0, service not avaiable.
 1.13909 +       *      1, service available.
 1.13910 +       * b2 = 0, service not avaiable.
 1.13911 +       *      1, service available.
 1.13912 +       *
 1.13913 +       * @see 3GPP TS 31.102 4.2.8.
 1.13914 +       */
 1.13915 +      let usimService = GECKO_ICC_SERVICES.usim[geckoService];
 1.13916 +      if (!usimService) {
 1.13917 +        return false;
 1.13918 +      }
 1.13919 +      usimService -= 1;
 1.13920 +      index = Math.floor(usimService / 8);
 1.13921 +      bitmask = 1 << ((usimService % 8) << 0);
 1.13922 +    }
 1.13923 +
 1.13924 +    return (serviceTable !== null) &&
 1.13925 +           (index < serviceTable.length) &&
 1.13926 +           ((serviceTable[index] & bitmask) !== 0);
 1.13927 +  },
 1.13928 +
 1.13929 +  /**
 1.13930 +   * Check if the string is of GSM default 7-bit coded alphabets with bit 8
 1.13931 +   * set to 0.
 1.13932 +   *
 1.13933 +   * @param str  String to be checked.
 1.13934 +   */
 1.13935 +  isGsm8BitAlphabet: function(str) {
 1.13936 +    if (!str) {
 1.13937 +      return false;
 1.13938 +    }
 1.13939 +
 1.13940 +    const langTable = PDU_NL_LOCKING_SHIFT_TABLES[PDU_NL_IDENTIFIER_DEFAULT];
 1.13941 +    const langShiftTable = PDU_NL_SINGLE_SHIFT_TABLES[PDU_NL_IDENTIFIER_DEFAULT];
 1.13942 +
 1.13943 +    for (let i = 0; i < str.length; i++) {
 1.13944 +      let c = str.charAt(i);
 1.13945 +      let octet = langTable.indexOf(c);
 1.13946 +      if (octet == -1) {
 1.13947 +        octet = langShiftTable.indexOf(c);
 1.13948 +        if (octet == -1) {
 1.13949 +          return false;
 1.13950 +        }
 1.13951 +      }
 1.13952 +    }
 1.13953 +
 1.13954 +    return true;
 1.13955 +  },
 1.13956 +
 1.13957 +  /**
 1.13958 +   * Parse MCC/MNC from IMSI. If there is no available value for the length of
 1.13959 +   * mnc, it will use the data in MCC table to parse.
 1.13960 +   *
 1.13961 +   * @param imsi
 1.13962 +   *        The imsi of icc.
 1.13963 +   * @param mncLength [optional]
 1.13964 +   *        The length of mnc.
 1.13965 +   *
 1.13966 +   * @return An object contains the parsing result of mcc and mnc.
 1.13967 +   *         Or null if any error occurred.
 1.13968 +   */
 1.13969 +  parseMccMncFromImsi: function(imsi, mncLength) {
 1.13970 +    if (!imsi) {
 1.13971 +      return null;
 1.13972 +    }
 1.13973 +
 1.13974 +    // MCC is the first 3 digits of IMSI.
 1.13975 +    let mcc = imsi.substr(0,3);
 1.13976 +    if (!mncLength) {
 1.13977 +      // Check the MCC table to decide the length of MNC.
 1.13978 +      let index = MCC_TABLE_FOR_MNC_LENGTH_IS_3.indexOf(mcc);
 1.13979 +      mncLength = (index !== -1) ? 3 : 2;
 1.13980 +    }
 1.13981 +    let mnc = imsi.substr(3, mncLength);
 1.13982 +    if (DEBUG) {
 1.13983 +      this.context.debug("IMSI: " + imsi + " MCC: " + mcc + " MNC: " + mnc);
 1.13984 +    }
 1.13985 +
 1.13986 +    return { mcc: mcc, mnc: mnc};
 1.13987 +  },
 1.13988 +};
 1.13989 +
 1.13990 +/**
 1.13991 + * Helper for ICC Contacts.
 1.13992 + */
 1.13993 +function ICCContactHelperObject(aContext) {
 1.13994 +  this.context = aContext;
 1.13995 +}
 1.13996 +ICCContactHelperObject.prototype = {
 1.13997 +  context: null,
 1.13998 +
 1.13999 +  /**
 1.14000 +   * Helper function to check DF_PHONEBOOK.
 1.14001 +   */
 1.14002 +  hasDfPhoneBook: function(appType) {
 1.14003 +    switch (appType) {
 1.14004 +      case CARD_APPTYPE_SIM:
 1.14005 +        return false;
 1.14006 +      case CARD_APPTYPE_USIM:
 1.14007 +        return true;
 1.14008 +      case CARD_APPTYPE_RUIM:
 1.14009 +        let ICCUtilsHelper = this.context.ICCUtilsHelper;
 1.14010 +        return ICCUtilsHelper.isICCServiceAvailable("ENHANCED_PHONEBOOK");
 1.14011 +      default:
 1.14012 +        return false;
 1.14013 +    }
 1.14014 +  },
 1.14015 +
 1.14016 +  /**
 1.14017 +   * Helper function to read ICC contacts.
 1.14018 +   *
 1.14019 +   * @param appType       One of CARD_APPTYPE_*.
 1.14020 +   * @param contactType   "adn" or "fdn".
 1.14021 +   * @param onsuccess     Callback to be called when success.
 1.14022 +   * @param onerror       Callback to be called when error.
 1.14023 +   */
 1.14024 +  readICCContacts: function(appType, contactType, onsuccess, onerror) {
 1.14025 +    let ICCRecordHelper = this.context.ICCRecordHelper;
 1.14026 +
 1.14027 +    switch (contactType) {
 1.14028 +      case "adn":
 1.14029 +        if (!this.hasDfPhoneBook(appType)) {
 1.14030 +          ICCRecordHelper.readADNLike(ICC_EF_ADN, onsuccess, onerror);
 1.14031 +        } else {
 1.14032 +          this.readUSimContacts(onsuccess, onerror);
 1.14033 +        }
 1.14034 +        break;
 1.14035 +      case "fdn":
 1.14036 +        ICCRecordHelper.readADNLike(ICC_EF_FDN, onsuccess, onerror);
 1.14037 +        break;
 1.14038 +      default:
 1.14039 +        if (DEBUG) {
 1.14040 +          this.context.debug("Unsupported contactType :" + contactType);
 1.14041 +        }
 1.14042 +        onerror(CONTACT_ERR_CONTACT_TYPE_NOT_SUPPORTED);
 1.14043 +        break;
 1.14044 +    }
 1.14045 +  },
 1.14046 +
 1.14047 +  /**
 1.14048 +   * Helper function to find free contact record.
 1.14049 +   *
 1.14050 +   * @param appType       One of CARD_APPTYPE_*.
 1.14051 +   * @param contactType   "adn" or "fdn".
 1.14052 +   * @param onsuccess     Callback to be called when success.
 1.14053 +   * @param onerror       Callback to be called when error.
 1.14054 +   */
 1.14055 +  findFreeICCContact: function(appType, contactType, onsuccess, onerror) {
 1.14056 +    let ICCRecordHelper = this.context.ICCRecordHelper;
 1.14057 +
 1.14058 +    switch (contactType) {
 1.14059 +      case "adn":
 1.14060 +        if (!this.hasDfPhoneBook(appType)) {
 1.14061 +          ICCRecordHelper.findFreeRecordId(ICC_EF_ADN, onsuccess.bind(null, 0), onerror);
 1.14062 +        } else {
 1.14063 +          let gotPbrCb = function gotPbrCb(pbrs) {
 1.14064 +            this.findUSimFreeADNRecordId(pbrs, onsuccess, onerror);
 1.14065 +          }.bind(this);
 1.14066 +
 1.14067 +          ICCRecordHelper.readPBR(gotPbrCb, onerror);
 1.14068 +        }
 1.14069 +        break;
 1.14070 +      case "fdn":
 1.14071 +        ICCRecordHelper.findFreeRecordId(ICC_EF_FDN, onsuccess.bind(null, 0), onerror);
 1.14072 +        break;
 1.14073 +      default:
 1.14074 +        if (DEBUG) {
 1.14075 +          this.context.debug("Unsupported contactType :" + contactType);
 1.14076 +        }
 1.14077 +        onerror(CONTACT_ERR_CONTACT_TYPE_NOT_SUPPORTED);
 1.14078 +        break;
 1.14079 +    }
 1.14080 +  },
 1.14081 +
 1.14082 +   /**
 1.14083 +    * Find free ADN record id in USIM.
 1.14084 +    *
 1.14085 +    * @param pbrs          All Phonebook Reference Files read.
 1.14086 +    * @param onsuccess     Callback to be called when success.
 1.14087 +    * @param onerror       Callback to be called when error.
 1.14088 +    */
 1.14089 +  findUSimFreeADNRecordId: function(pbrs, onsuccess, onerror) {
 1.14090 +    let ICCRecordHelper = this.context.ICCRecordHelper;
 1.14091 +
 1.14092 +    (function findFreeRecordId(pbrIndex) {
 1.14093 +      if (pbrIndex >= pbrs.length) {
 1.14094 +        if (DEBUG) {
 1.14095 +          this.context.debug(CONTACT_ERR_NO_FREE_RECORD_FOUND);
 1.14096 +        }
 1.14097 +        onerror(CONTACT_ERR_NO_FREE_RECORD_FOUND);
 1.14098 +        return;
 1.14099 +      }
 1.14100 +
 1.14101 +      let pbr = pbrs[pbrIndex];
 1.14102 +      ICCRecordHelper.findFreeRecordId(
 1.14103 +        pbr.adn.fileId,
 1.14104 +        onsuccess.bind(this, pbrIndex),
 1.14105 +        findFreeRecordId.bind(null, pbrIndex + 1));
 1.14106 +    })(0);
 1.14107 +  },
 1.14108 +
 1.14109 +  /**
 1.14110 +   * Helper function to add a new ICC contact.
 1.14111 +   *
 1.14112 +   * @param appType       One of CARD_APPTYPE_*.
 1.14113 +   * @param contactType   "adn" or "fdn".
 1.14114 +   * @param contact       The contact will be added.
 1.14115 +   * @param pin2          PIN2 is required for FDN.
 1.14116 +   * @param onsuccess     Callback to be called when success.
 1.14117 +   * @param onerror       Callback to be called when error.
 1.14118 +   */
 1.14119 +  addICCContact: function(appType, contactType, contact, pin2, onsuccess, onerror) {
 1.14120 +    let foundFreeCb = (function foundFreeCb(pbrIndex, recordId) {
 1.14121 +      contact.pbrIndex = pbrIndex;
 1.14122 +      contact.recordId = recordId;
 1.14123 +      this.updateICCContact(appType, contactType, contact, pin2, onsuccess, onerror);
 1.14124 +    }).bind(this);
 1.14125 +
 1.14126 +    // Find free record first.
 1.14127 +    this.findFreeICCContact(appType, contactType, foundFreeCb, onerror);
 1.14128 +  },
 1.14129 +
 1.14130 +  /**
 1.14131 +   * Helper function to update ICC contact.
 1.14132 +   *
 1.14133 +   * @param appType       One of CARD_APPTYPE_*.
 1.14134 +   * @param contactType   "adn" or "fdn".
 1.14135 +   * @param contact       The contact will be updated.
 1.14136 +   * @param pin2          PIN2 is required for FDN.
 1.14137 +   * @param onsuccess     Callback to be called when success.
 1.14138 +   * @param onerror       Callback to be called when error.
 1.14139 +   */
 1.14140 +  updateICCContact: function(appType, contactType, contact, pin2, onsuccess, onerror) {
 1.14141 +    let ICCRecordHelper = this.context.ICCRecordHelper;
 1.14142 +
 1.14143 +    switch (contactType) {
 1.14144 +      case "adn":
 1.14145 +        if (!this.hasDfPhoneBook(appType)) {
 1.14146 +          ICCRecordHelper.updateADNLike(ICC_EF_ADN, contact, null, onsuccess, onerror);
 1.14147 +        } else {
 1.14148 +          this.updateUSimContact(contact, onsuccess, onerror);
 1.14149 +        }
 1.14150 +        break;
 1.14151 +      case "fdn":
 1.14152 +        if (!pin2) {
 1.14153 +          onerror(GECKO_ERROR_SIM_PIN2);
 1.14154 +          return;
 1.14155 +        }
 1.14156 +        ICCRecordHelper.updateADNLike(ICC_EF_FDN, contact, pin2, onsuccess, onerror);
 1.14157 +        break;
 1.14158 +      default:
 1.14159 +        if (DEBUG) {
 1.14160 +          this.context.debug("Unsupported contactType :" + contactType);
 1.14161 +        }
 1.14162 +        onerror(CONTACT_ERR_CONTACT_TYPE_NOT_SUPPORTED);
 1.14163 +        break;
 1.14164 +    }
 1.14165 +  },
 1.14166 +
 1.14167 +  /**
 1.14168 +   * Read contacts from USIM.
 1.14169 +   *
 1.14170 +   * @param onsuccess     Callback to be called when success.
 1.14171 +   * @param onerror       Callback to be called when error.
 1.14172 +   */
 1.14173 +  readUSimContacts: function(onsuccess, onerror) {
 1.14174 +    let gotPbrCb = function gotPbrCb(pbrs) {
 1.14175 +      this.readAllPhonebookSets(pbrs, onsuccess, onerror);
 1.14176 +    }.bind(this);
 1.14177 +
 1.14178 +    this.context.ICCRecordHelper.readPBR(gotPbrCb, onerror);
 1.14179 +  },
 1.14180 +
 1.14181 +  /**
 1.14182 +   * Read all Phonebook sets.
 1.14183 +   *
 1.14184 +   * @param pbrs          All Phonebook Reference Files read.
 1.14185 +   * @param onsuccess     Callback to be called when success.
 1.14186 +   * @param onerror       Callback to be called when error.
 1.14187 +   */
 1.14188 +  readAllPhonebookSets: function(pbrs, onsuccess, onerror) {
 1.14189 +    let allContacts = [], pbrIndex = 0;
 1.14190 +    let readPhonebook = function readPhonebook(contacts) {
 1.14191 +      if (contacts) {
 1.14192 +        allContacts = allContacts.concat(contacts);
 1.14193 +      }
 1.14194 +
 1.14195 +      let cLen = contacts ? contacts.length : 0;
 1.14196 +      for (let i = 0; i < cLen; i++) {
 1.14197 +        contacts[i].pbrIndex = pbrIndex;
 1.14198 +      }
 1.14199 +
 1.14200 +      pbrIndex++;
 1.14201 +      if (pbrIndex >= pbrs.length) {
 1.14202 +        if (onsuccess) {
 1.14203 +          onsuccess(allContacts);
 1.14204 +        }
 1.14205 +        return;
 1.14206 +      }
 1.14207 +
 1.14208 +      this.readPhonebookSet(pbrs[pbrIndex], readPhonebook, onerror);
 1.14209 +    }.bind(this);
 1.14210 +
 1.14211 +    this.readPhonebookSet(pbrs[pbrIndex], readPhonebook, onerror);
 1.14212 +  },
 1.14213 +
 1.14214 +  /**
 1.14215 +   * Read from Phonebook Reference File.
 1.14216 +   *
 1.14217 +   * @param pbr           Phonebook Reference File to be read.
 1.14218 +   * @param onsuccess     Callback to be called when success.
 1.14219 +   * @param onerror       Callback to be called when error.
 1.14220 +   */
 1.14221 +  readPhonebookSet: function(pbr, onsuccess, onerror) {
 1.14222 +    let gotAdnCb = function gotAdnCb(contacts) {
 1.14223 +      this.readSupportedPBRFields(pbr, contacts, onsuccess, onerror);
 1.14224 +    }.bind(this);
 1.14225 +
 1.14226 +    this.context.ICCRecordHelper.readADNLike(pbr.adn.fileId, gotAdnCb, onerror);
 1.14227 +  },
 1.14228 +
 1.14229 +  /**
 1.14230 +   * Read supported Phonebook fields.
 1.14231 +   *
 1.14232 +   * @param pbr         Phone Book Reference file.
 1.14233 +   * @param contacts    Contacts stored on ICC.
 1.14234 +   * @param onsuccess   Callback to be called when success.
 1.14235 +   * @param onerror     Callback to be called when error.
 1.14236 +   */
 1.14237 +  readSupportedPBRFields: function(pbr, contacts, onsuccess, onerror) {
 1.14238 +    let fieldIndex = 0;
 1.14239 +    (function readField() {
 1.14240 +      let field = USIM_PBR_FIELDS[fieldIndex];
 1.14241 +      fieldIndex += 1;
 1.14242 +      if (!field) {
 1.14243 +        if (onsuccess) {
 1.14244 +          onsuccess(contacts);
 1.14245 +        }
 1.14246 +        return;
 1.14247 +      }
 1.14248 +
 1.14249 +      this.readPhonebookField(pbr, contacts, field, readField.bind(this), onerror);
 1.14250 +    }).call(this);
 1.14251 +  },
 1.14252 +
 1.14253 +  /**
 1.14254 +   * Read Phonebook field.
 1.14255 +   *
 1.14256 +   * @param pbr           The phonebook reference file.
 1.14257 +   * @param contacts      Contacts stored on ICC.
 1.14258 +   * @param field         Phonebook field to be retrieved.
 1.14259 +   * @param onsuccess     Callback to be called when success.
 1.14260 +   * @param onerror       Callback to be called when error.
 1.14261 +   */
 1.14262 +  readPhonebookField: function(pbr, contacts, field, onsuccess, onerror) {
 1.14263 +    if (!pbr[field]) {
 1.14264 +      if (onsuccess) {
 1.14265 +        onsuccess(contacts);
 1.14266 +      }
 1.14267 +      return;
 1.14268 +    }
 1.14269 +
 1.14270 +    (function doReadContactField(n) {
 1.14271 +      if (n >= contacts.length) {
 1.14272 +        // All contact's fields are read.
 1.14273 +        if (onsuccess) {
 1.14274 +          onsuccess(contacts);
 1.14275 +        }
 1.14276 +        return;
 1.14277 +      }
 1.14278 +
 1.14279 +      // get n-th contact's field.
 1.14280 +      this.readContactField(pbr, contacts[n], field,
 1.14281 +                            doReadContactField.bind(this, n + 1), onerror);
 1.14282 +    }).call(this, 0);
 1.14283 +  },
 1.14284 +
 1.14285 +  /**
 1.14286 +   * Read contact's field from USIM.
 1.14287 +   *
 1.14288 +   * @param pbr           The phonebook reference file.
 1.14289 +   * @param contact       The contact needs to get field.
 1.14290 +   * @param field         Phonebook field to be retrieved.
 1.14291 +   * @param onsuccess     Callback to be called when success.
 1.14292 +   * @param onerror       Callback to be called when error.
 1.14293 +   */
 1.14294 +  readContactField: function(pbr, contact, field, onsuccess, onerror) {
 1.14295 +    let gotRecordIdCb = function gotRecordIdCb(recordId) {
 1.14296 +      if (recordId == 0xff) {
 1.14297 +        if (onsuccess) {
 1.14298 +          onsuccess();
 1.14299 +        }
 1.14300 +        return;
 1.14301 +      }
 1.14302 +
 1.14303 +      let fileId = pbr[field].fileId;
 1.14304 +      let fileType = pbr[field].fileType;
 1.14305 +      let gotFieldCb = function gotFieldCb(value) {
 1.14306 +        if (value) {
 1.14307 +          // Move anr0 anr1,.. into anr[].
 1.14308 +          if (field.startsWith(USIM_PBR_ANR)) {
 1.14309 +            if (!contact[USIM_PBR_ANR]) {
 1.14310 +              contact[USIM_PBR_ANR] = [];
 1.14311 +            }
 1.14312 +            contact[USIM_PBR_ANR].push(value);
 1.14313 +          } else {
 1.14314 +            contact[field] = value;
 1.14315 +          }
 1.14316 +        }
 1.14317 +
 1.14318 +        if (onsuccess) {
 1.14319 +          onsuccess();
 1.14320 +        }
 1.14321 +      }.bind(this);
 1.14322 +
 1.14323 +      let ICCRecordHelper = this.context.ICCRecordHelper;
 1.14324 +      // Detect EF to be read, for anr, it could have anr0, anr1,...
 1.14325 +      let ef = field.startsWith(USIM_PBR_ANR) ? USIM_PBR_ANR : field;
 1.14326 +      switch (ef) {
 1.14327 +        case USIM_PBR_EMAIL:
 1.14328 +          ICCRecordHelper.readEmail(fileId, fileType, recordId, gotFieldCb, onerror);
 1.14329 +          break;
 1.14330 +        case USIM_PBR_ANR:
 1.14331 +          ICCRecordHelper.readANR(fileId, fileType, recordId, gotFieldCb, onerror);
 1.14332 +          break;
 1.14333 +        default:
 1.14334 +          if (DEBUG) {
 1.14335 +            this.context.debug("Unsupported field :" + field);
 1.14336 +          }
 1.14337 +          onerror(CONTACT_ERR_FIELD_NOT_SUPPORTED);
 1.14338 +          break;
 1.14339 +      }
 1.14340 +    }.bind(this);
 1.14341 +
 1.14342 +    this.getContactFieldRecordId(pbr, contact, field, gotRecordIdCb, onerror);
 1.14343 +  },
 1.14344 +
 1.14345 +  /**
 1.14346 +   * Get the recordId.
 1.14347 +   *
 1.14348 +   * If the fileType of field is ICC_USIM_TYPE1_TAG, use corresponding ADN recordId.
 1.14349 +   * otherwise get the recordId from IAP.
 1.14350 +   *
 1.14351 +   * @see TS 131.102, clause 4.4.2.2
 1.14352 +   *
 1.14353 +   * @param pbr          The phonebook reference file.
 1.14354 +   * @param contact      The contact will be updated.
 1.14355 +   * @param onsuccess    Callback to be called when success.
 1.14356 +   * @param onerror      Callback to be called when error.
 1.14357 +   */
 1.14358 +  getContactFieldRecordId: function(pbr, contact, field, onsuccess, onerror) {
 1.14359 +    if (pbr[field].fileType == ICC_USIM_TYPE1_TAG) {
 1.14360 +      // If the file type is ICC_USIM_TYPE1_TAG, use corresponding ADN recordId.
 1.14361 +      if (onsuccess) {
 1.14362 +        onsuccess(contact.recordId);
 1.14363 +      }
 1.14364 +    } else if (pbr[field].fileType == ICC_USIM_TYPE2_TAG) {
 1.14365 +      // If the file type is ICC_USIM_TYPE2_TAG, the recordId shall be got from IAP.
 1.14366 +      let gotIapCb = function gotIapCb(iap) {
 1.14367 +        let indexInIAP = pbr[field].indexInIAP;
 1.14368 +        let recordId = iap[indexInIAP];
 1.14369 +
 1.14370 +        if (onsuccess) {
 1.14371 +          onsuccess(recordId);
 1.14372 +        }
 1.14373 +      }.bind(this);
 1.14374 +
 1.14375 +      this.context.ICCRecordHelper.readIAP(pbr.iap.fileId, contact.recordId,
 1.14376 +                                           gotIapCb, onerror);
 1.14377 +    } else {
 1.14378 +      if (DEBUG) {
 1.14379 +        this.context.debug("USIM PBR files in Type 3 format are not supported.");
 1.14380 +      }
 1.14381 +      onerror(CONTACT_ERR_REQUEST_NOT_SUPPORTED);
 1.14382 +    }
 1.14383 +  },
 1.14384 +
 1.14385 +  /**
 1.14386 +   * Update USIM contact.
 1.14387 +   *
 1.14388 +   * @param contact       The contact will be updated.
 1.14389 +   * @param onsuccess     Callback to be called when success.
 1.14390 +   * @param onerror       Callback to be called when error.
 1.14391 +   */
 1.14392 +  updateUSimContact: function(contact, onsuccess, onerror) {
 1.14393 +    let gotPbrCb = function gotPbrCb(pbrs) {
 1.14394 +      let pbr = pbrs[contact.pbrIndex];
 1.14395 +      if (!pbr) {
 1.14396 +        if (DEBUG) {
 1.14397 +          this.context.debug(CONTACT_ERR_CANNOT_ACCESS_PHONEBOOK);
 1.14398 +        }
 1.14399 +        onerror(CONTACT_ERR_CANNOT_ACCESS_PHONEBOOK);
 1.14400 +        return;
 1.14401 +      }
 1.14402 +      this.updatePhonebookSet(pbr, contact, onsuccess, onerror);
 1.14403 +    }.bind(this);
 1.14404 +
 1.14405 +    this.context.ICCRecordHelper.readPBR(gotPbrCb, onerror);
 1.14406 +  },
 1.14407 +
 1.14408 +  /**
 1.14409 +   * Update fields in Phonebook Reference File.
 1.14410 +   *
 1.14411 +   * @param pbr           Phonebook Reference File to be read.
 1.14412 +   * @param onsuccess     Callback to be called when success.
 1.14413 +   * @param onerror       Callback to be called when error.
 1.14414 +   */
 1.14415 +  updatePhonebookSet: function(pbr, contact, onsuccess, onerror) {
 1.14416 +    let updateAdnCb = function() {
 1.14417 +      this.updateSupportedPBRFields(pbr, contact, onsuccess, onerror);
 1.14418 +    }.bind(this);
 1.14419 +
 1.14420 +    this.context.ICCRecordHelper.updateADNLike(pbr.adn.fileId, contact, null,
 1.14421 +                                               updateAdnCb, onerror);
 1.14422 +  },
 1.14423 +
 1.14424 +  /**
 1.14425 +   * Update supported Phonebook fields.
 1.14426 +   *
 1.14427 +   * @param pbr         Phone Book Reference file.
 1.14428 +   * @param contact     Contact to be updated.
 1.14429 +   * @param onsuccess   Callback to be called when success.
 1.14430 +   * @param onerror     Callback to be called when error.
 1.14431 +   */
 1.14432 +  updateSupportedPBRFields: function(pbr, contact, onsuccess, onerror) {
 1.14433 +    let fieldIndex = 0;
 1.14434 +    (function updateField() {
 1.14435 +      let field = USIM_PBR_FIELDS[fieldIndex];
 1.14436 +      fieldIndex += 1;
 1.14437 +      if (!field) {
 1.14438 +        if (onsuccess) {
 1.14439 +          onsuccess();
 1.14440 +        }
 1.14441 +        return;
 1.14442 +      }
 1.14443 +
 1.14444 +      // Check if PBR has this field.
 1.14445 +      if (!pbr[field]) {
 1.14446 +        updateField.call(this);
 1.14447 +        return;
 1.14448 +      }
 1.14449 +
 1.14450 +      this.updateContactField(pbr, contact, field, updateField.bind(this), onerror);
 1.14451 +    }).call(this);
 1.14452 +  },
 1.14453 +
 1.14454 +  /**
 1.14455 +   * Update contact's field from USIM.
 1.14456 +   *
 1.14457 +   * @param pbr           The phonebook reference file.
 1.14458 +   * @param contact       The contact needs to be updated.
 1.14459 +   * @param field         Phonebook field to be updated.
 1.14460 +   * @param onsuccess     Callback to be called when success.
 1.14461 +   * @param onerror       Callback to be called when error.
 1.14462 +   */
 1.14463 +  updateContactField: function(pbr, contact, field, onsuccess, onerror) {
 1.14464 +    if (pbr[field].fileType === ICC_USIM_TYPE1_TAG) {
 1.14465 +      this.updateContactFieldType1(pbr, contact, field, onsuccess, onerror);
 1.14466 +    } else if (pbr[field].fileType === ICC_USIM_TYPE2_TAG) {
 1.14467 +      this.updateContactFieldType2(pbr, contact, field, onsuccess, onerror);
 1.14468 +    } else {
 1.14469 +      if (DEBUG) {
 1.14470 +        this.context.debug("USIM PBR files in Type 3 format are not supported.");
 1.14471 +      }
 1.14472 +      onerror(CONTACT_ERR_REQUEST_NOT_SUPPORTED);
 1.14473 +    }
 1.14474 +  },
 1.14475 +
 1.14476 +  /**
 1.14477 +   * Update Type 1 USIM contact fields.
 1.14478 +   *
 1.14479 +   * @param pbr           The phonebook reference file.
 1.14480 +   * @param contact       The contact needs to be updated.
 1.14481 +   * @param field         Phonebook field to be updated.
 1.14482 +   * @param onsuccess     Callback to be called when success.
 1.14483 +   * @param onerror       Callback to be called when error.
 1.14484 +   */
 1.14485 +  updateContactFieldType1: function(pbr, contact, field, onsuccess, onerror) {
 1.14486 +    let ICCRecordHelper = this.context.ICCRecordHelper;
 1.14487 +
 1.14488 +    if (field === USIM_PBR_EMAIL) {
 1.14489 +      ICCRecordHelper.updateEmail(pbr, contact.recordId, contact.email, null, onsuccess, onerror);
 1.14490 +    } else if (field === USIM_PBR_ANR0) {
 1.14491 +      let anr = Array.isArray(contact.anr) ? contact.anr[0] : null;
 1.14492 +      ICCRecordHelper.updateANR(pbr, contact.recordId, anr, null, onsuccess, onerror);
 1.14493 +    } else {
 1.14494 +     if (DEBUG) {
 1.14495 +       this.context.debug("Unsupported field :" + field);
 1.14496 +     }
 1.14497 +     onerror(CONTACT_ERR_FIELD_NOT_SUPPORTED);
 1.14498 +    }
 1.14499 +  },
 1.14500 +
 1.14501 +  /**
 1.14502 +   * Update Type 2 USIM contact fields.
 1.14503 +   *
 1.14504 +   * @param pbr           The phonebook reference file.
 1.14505 +   * @param contact       The contact needs to be updated.
 1.14506 +   * @param field         Phonebook field to be updated.
 1.14507 +   * @param onsuccess     Callback to be called when success.
 1.14508 +   * @param onerror       Callback to be called when error.
 1.14509 +   */
 1.14510 +  updateContactFieldType2: function(pbr, contact, field, onsuccess, onerror) {
 1.14511 +    let ICCRecordHelper = this.context.ICCRecordHelper;
 1.14512 +
 1.14513 +    // Case 1 : EF_IAP[adnRecordId] doesn't have a value(0xff)
 1.14514 +    //   Find a free recordId for EF_field
 1.14515 +    //   Update field with that free recordId.
 1.14516 +    //   Update IAP.
 1.14517 +    //
 1.14518 +    // Case 2: EF_IAP[adnRecordId] has a value
 1.14519 +    //   update EF_field[iap[field.indexInIAP]]
 1.14520 +
 1.14521 +    let gotIapCb = function gotIapCb(iap) {
 1.14522 +      let recordId = iap[pbr[field].indexInIAP];
 1.14523 +      if (recordId === 0xff) {
 1.14524 +        // If the value in IAP[index] is 0xff, which means the contact stored on
 1.14525 +        // the SIM doesn't have the additional attribute (email or anr).
 1.14526 +        // So if the contact to be updated doesn't have the attribute either,
 1.14527 +        // we don't have to update it.
 1.14528 +        if ((field === USIM_PBR_EMAIL && contact.email) ||
 1.14529 +            (field === USIM_PBR_ANR0 &&
 1.14530 +             (Array.isArray(contact.anr) && contact.anr[0]))) {
 1.14531 +          // Case 1.
 1.14532 +          this.addContactFieldType2(pbr, contact, field, onsuccess, onerror);
 1.14533 +        } else {
 1.14534 +          if (onsuccess) {
 1.14535 +            onsuccess();
 1.14536 +          }
 1.14537 +        }
 1.14538 +        return;
 1.14539 +      }
 1.14540 +
 1.14541 +      // Case 2.
 1.14542 +      if (field === USIM_PBR_EMAIL) {
 1.14543 +        ICCRecordHelper.updateEmail(pbr, recordId, contact.email, contact.recordId, onsuccess, onerror);
 1.14544 +      } else if (field === USIM_PBR_ANR0) {
 1.14545 +        let anr = Array.isArray(contact.anr) ? contact.anr[0] : null;
 1.14546 +        ICCRecordHelper.updateANR(pbr, recordId, anr, contact.recordId, onsuccess, onerror);
 1.14547 +      } else {
 1.14548 +        if (DEBUG) {
 1.14549 +          this.context.debug("Unsupported field :" + field);
 1.14550 +        }
 1.14551 +        onerror(CONTACT_ERR_FIELD_NOT_SUPPORTED);
 1.14552 +      }
 1.14553 +
 1.14554 +    }.bind(this);
 1.14555 +
 1.14556 +    ICCRecordHelper.readIAP(pbr.iap.fileId, contact.recordId, gotIapCb, onerror);
 1.14557 +  },
 1.14558 +
 1.14559 +  /**
 1.14560 +   * Add Type 2 USIM contact fields.
 1.14561 +   *
 1.14562 +   * @param pbr           The phonebook reference file.
 1.14563 +   * @param contact       The contact needs to be updated.
 1.14564 +   * @param field         Phonebook field to be updated.
 1.14565 +   * @param onsuccess     Callback to be called when success.
 1.14566 +   * @param onerror       Callback to be called when error.
 1.14567 +   */
 1.14568 +  addContactFieldType2: function(pbr, contact, field, onsuccess, onerror) {
 1.14569 +    let ICCRecordHelper = this.context.ICCRecordHelper;
 1.14570 +
 1.14571 +    let successCb = function successCb(recordId) {
 1.14572 +      let updateCb = function updateCb() {
 1.14573 +        this.updateContactFieldIndexInIAP(pbr, contact.recordId, field, recordId, onsuccess, onerror);
 1.14574 +      }.bind(this);
 1.14575 +
 1.14576 +      if (field === USIM_PBR_EMAIL) {
 1.14577 +        ICCRecordHelper.updateEmail(pbr, recordId, contact.email, contact.recordId, updateCb, onerror);
 1.14578 +      } else if (field === USIM_PBR_ANR0) {
 1.14579 +        ICCRecordHelper.updateANR(pbr, recordId, contact.anr[0], contact.recordId, updateCb, onerror);
 1.14580 +      }
 1.14581 +    }.bind(this);
 1.14582 +
 1.14583 +    let errorCb = function errorCb(errorMsg) {
 1.14584 +      if (DEBUG) {
 1.14585 +        this.context.debug(errorMsg + " USIM field " + field);
 1.14586 +      }
 1.14587 +      onerror(errorMsg);
 1.14588 +    }.bind(this);
 1.14589 +
 1.14590 +    ICCRecordHelper.findFreeRecordId(pbr[field].fileId, successCb, errorCb);
 1.14591 +  },
 1.14592 +
 1.14593 +  /**
 1.14594 +   * Update IAP value.
 1.14595 +   *
 1.14596 +   * @param pbr           The phonebook reference file.
 1.14597 +   * @param recordNumber  The record identifier of EF_IAP.
 1.14598 +   * @param field         Phonebook field.
 1.14599 +   * @param value         The value of 'field' in IAP.
 1.14600 +   * @param onsuccess     Callback to be called when success.
 1.14601 +   * @param onerror       Callback to be called when error.
 1.14602 +   *
 1.14603 +   */
 1.14604 +  updateContactFieldIndexInIAP: function(pbr, recordNumber, field, value, onsuccess, onerror) {
 1.14605 +    let ICCRecordHelper = this.context.ICCRecordHelper;
 1.14606 +
 1.14607 +    let gotIAPCb = function gotIAPCb(iap) {
 1.14608 +      iap[pbr[field].indexInIAP] = value;
 1.14609 +      ICCRecordHelper.updateIAP(pbr.iap.fileId, recordNumber, iap, onsuccess, onerror);
 1.14610 +    }.bind(this);
 1.14611 +    ICCRecordHelper.readIAP(pbr.iap.fileId, recordNumber, gotIAPCb, onerror);
 1.14612 +  },
 1.14613 +};
 1.14614 +
 1.14615 +/**
 1.14616 + * Global stuff.
 1.14617 + */
 1.14618 +
 1.14619 +function Context(aClientId) {
 1.14620 +  this.clientId = aClientId;
 1.14621 +
 1.14622 +  this.Buf = new BufObject(this);
 1.14623 +  this.Buf.init();
 1.14624 +
 1.14625 +  this.RIL = new RilObject(this);
 1.14626 +  this.RIL.initRILState();
 1.14627 +}
 1.14628 +Context.prototype = {
 1.14629 +  clientId: null,
 1.14630 +  Buf: null,
 1.14631 +  RIL: null,
 1.14632 +
 1.14633 +  debug: function(aMessage) {
 1.14634 +    GLOBAL.debug("[" + this.clientId + "] " + aMessage);
 1.14635 +  }
 1.14636 +};
 1.14637 +
 1.14638 +(function() {
 1.14639 +  let lazySymbols = [
 1.14640 +    "BerTlvHelper", "BitBufferHelper", "CdmaPDUHelper",
 1.14641 +    "ComprehensionTlvHelper", "GsmPDUHelper", "ICCContactHelper",
 1.14642 +    "ICCFileHelper", "ICCIOHelper", "ICCPDUHelper", "ICCRecordHelper",
 1.14643 +    "ICCUtilsHelper", "RuimRecordHelper", "SimRecordHelper",
 1.14644 +    "StkCommandParamsFactory", "StkProactiveCmdHelper",
 1.14645 +  ];
 1.14646 +
 1.14647 +  for (let i = 0; i < lazySymbols.length; i++) {
 1.14648 +    let symbol = lazySymbols[i];
 1.14649 +    Object.defineProperty(Context.prototype, symbol, {
 1.14650 +      get: function() {
 1.14651 +        let real = new GLOBAL[symbol + "Object"](this);
 1.14652 +        Object.defineProperty(this, symbol, {
 1.14653 +          value: real,
 1.14654 +          enumerable: true
 1.14655 +        });
 1.14656 +        return real;
 1.14657 +      },
 1.14658 +      configurable: true,
 1.14659 +      enumerable: true
 1.14660 +    });
 1.14661 +  }
 1.14662 +})();
 1.14663 +
 1.14664 +let ContextPool = {
 1.14665 +  _contexts: [],
 1.14666 +
 1.14667 +  handleRilMessage: function(aClientId, aUint8Array) {
 1.14668 +    let context = this._contexts[aClientId];
 1.14669 +    context.Buf.processIncoming(aUint8Array);
 1.14670 +  },
 1.14671 +
 1.14672 +  handleChromeMessage: function(aMessage) {
 1.14673 +    let clientId = aMessage.rilMessageClientId;
 1.14674 +    if (clientId != null) {
 1.14675 +      let context = this._contexts[clientId];
 1.14676 +      context.RIL.handleChromeMessage(aMessage);
 1.14677 +      return;
 1.14678 +    }
 1.14679 +
 1.14680 +    if (DEBUG) debug("Received global chrome message " + JSON.stringify(aMessage));
 1.14681 +    let method = this[aMessage.rilMessageType];
 1.14682 +    if (typeof method != "function") {
 1.14683 +      if (DEBUG) {
 1.14684 +        debug("Don't know what to do");
 1.14685 +      }
 1.14686 +      return;
 1.14687 +    }
 1.14688 +    method.call(this, aMessage);
 1.14689 +  },
 1.14690 +
 1.14691 +  setInitialOptions: function(aOptions) {
 1.14692 +    DEBUG = DEBUG_WORKER || aOptions.debug;
 1.14693 +    RIL_EMERGENCY_NUMBERS = aOptions.rilEmergencyNumbers;
 1.14694 +    RIL_CELLBROADCAST_DISABLED = aOptions.cellBroadcastDisabled;
 1.14695 +    RIL_CLIR_MODE = aOptions.clirMode;
 1.14696 +
 1.14697 +    let quirks = aOptions.quirks;
 1.14698 +    RILQUIRKS_CALLSTATE_EXTRA_UINT32 = quirks.callstateExtraUint32;
 1.14699 +    RILQUIRKS_V5_LEGACY = quirks.v5Legacy;
 1.14700 +    RILQUIRKS_REQUEST_USE_DIAL_EMERGENCY_CALL = quirks.requestUseDialEmergencyCall;
 1.14701 +    RILQUIRKS_SIM_APP_STATE_EXTRA_FIELDS = quirks.simAppStateExtraFields;
 1.14702 +    RILQUIRKS_EXTRA_UINT32_2ND_CALL = quirks.extraUint2ndCall;
 1.14703 +    RILQUIRKS_HAVE_QUERY_ICC_LOCK_RETRY_COUNT = quirks.haveQueryIccLockRetryCount;
 1.14704 +    RILQUIRKS_SEND_STK_PROFILE_DOWNLOAD = quirks.sendStkProfileDownload;
 1.14705 +    RILQUIRKS_DATA_REGISTRATION_ON_DEMAND = quirks.dataRegistrationOnDemand;
 1.14706 +  },
 1.14707 +
 1.14708 +  registerClient: function(aOptions) {
 1.14709 +    let clientId = aOptions.clientId;
 1.14710 +    this._contexts[clientId] = new Context(clientId);
 1.14711 +  },
 1.14712 +};
 1.14713 +
 1.14714 +function onRILMessage(aClientId, aUint8Array) {
 1.14715 +  ContextPool.handleRilMessage(aClientId, aUint8Array);
 1.14716 +}
 1.14717 +
 1.14718 +onmessage = function onmessage(event) {
 1.14719 +  ContextPool.handleChromeMessage(event.data);
 1.14720 +};
 1.14721 +
 1.14722 +onerror = function onerror(event) {
 1.14723 +  if (DEBUG) debug("onerror" + event.message + "\n");
 1.14724 +};

mercurial