|
1 /* Copyright 2012 Mozilla Foundation and Mozilla contributors |
|
2 * |
|
3 * Licensed under the Apache License, Version 2.0 (the "License"); |
|
4 * you may not use this file except in compliance with the License. |
|
5 * You may obtain a copy of the License at |
|
6 * |
|
7 * http://www.apache.org/licenses/LICENSE-2.0 |
|
8 * |
|
9 * Unless required by applicable law or agreed to in writing, software |
|
10 * distributed under the License is distributed on an "AS IS" BASIS, |
|
11 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
|
12 * See the License for the specific language governing permissions and |
|
13 * limitations under the License. |
|
14 */ |
|
15 |
|
16 /** |
|
17 * This file implements the RIL worker thread. It communicates with |
|
18 * the main thread to provide a high-level API to the phone's RIL |
|
19 * stack, and with the RIL IPC thread to communicate with the RIL |
|
20 * device itself. These communication channels use message events as |
|
21 * known from Web Workers: |
|
22 * |
|
23 * - postMessage()/"message" events for main thread communication |
|
24 * |
|
25 * - postRILMessage()/"RILMessageEvent" events for RIL IPC thread |
|
26 * communication. |
|
27 * |
|
28 * The two main objects in this file represent individual parts of this |
|
29 * communication chain: |
|
30 * |
|
31 * - RILMessageEvent -> Buf -> RIL -> postMessage() -> nsIRadioInterfaceLayer |
|
32 * - nsIRadioInterfaceLayer -> postMessage() -> RIL -> Buf -> postRILMessage() |
|
33 * |
|
34 * Note: The code below is purposely lean on abstractions to be as lean in |
|
35 * terms of object allocations. As a result, it may look more like C than |
|
36 * JavaScript, and that's intended. |
|
37 */ |
|
38 |
|
39 "use strict"; |
|
40 |
|
41 importScripts("ril_consts.js"); |
|
42 importScripts("resource://gre/modules/workers/require.js"); |
|
43 |
|
44 // set to true in ril_consts.js to see debug messages |
|
45 let DEBUG = DEBUG_WORKER; |
|
46 let GLOBAL = this; |
|
47 |
|
48 if (!this.debug) { |
|
49 // Debugging stub that goes nowhere. |
|
50 this.debug = function debug(message) { |
|
51 dump("RIL Worker: " + message + "\n"); |
|
52 }; |
|
53 } |
|
54 |
|
55 let RIL_CELLBROADCAST_DISABLED; |
|
56 let RIL_CLIR_MODE; |
|
57 let RIL_EMERGENCY_NUMBERS; |
|
58 const DEFAULT_EMERGENCY_NUMBERS = ["112", "911"]; |
|
59 |
|
60 // Timeout value for emergency callback mode. |
|
61 const EMERGENCY_CB_MODE_TIMEOUT_MS = 300000; // 5 mins = 300000 ms. |
|
62 |
|
63 const ICC_MAX_LINEAR_FIXED_RECORDS = 0xfe; |
|
64 |
|
65 // MMI match groups |
|
66 const MMI_MATCH_GROUP_FULL_MMI = 1; |
|
67 const MMI_MATCH_GROUP_MMI_PROCEDURE = 2; |
|
68 const MMI_MATCH_GROUP_SERVICE_CODE = 3; |
|
69 const MMI_MATCH_GROUP_SIA = 5; |
|
70 const MMI_MATCH_GROUP_SIB = 7; |
|
71 const MMI_MATCH_GROUP_SIC = 9; |
|
72 const MMI_MATCH_GROUP_PWD_CONFIRM = 11; |
|
73 const MMI_MATCH_GROUP_DIALING_NUMBER = 12; |
|
74 |
|
75 const MMI_MAX_LENGTH_SHORT_CODE = 2; |
|
76 |
|
77 const MMI_END_OF_USSD = "#"; |
|
78 |
|
79 // Should match the value we set in dom/telephony/TelephonyCommon.h |
|
80 const OUTGOING_PLACEHOLDER_CALL_INDEX = 0xffffffff; |
|
81 |
|
82 let RILQUIRKS_CALLSTATE_EXTRA_UINT32; |
|
83 // This may change at runtime since in RIL v6 and later, we get the version |
|
84 // number via the UNSOLICITED_RIL_CONNECTED parcel. |
|
85 let RILQUIRKS_V5_LEGACY; |
|
86 let RILQUIRKS_REQUEST_USE_DIAL_EMERGENCY_CALL; |
|
87 let RILQUIRKS_SIM_APP_STATE_EXTRA_FIELDS; |
|
88 // Needed for call-waiting on Peak device |
|
89 let RILQUIRKS_EXTRA_UINT32_2ND_CALL; |
|
90 // On the emulator we support querying the number of lock retries |
|
91 let RILQUIRKS_HAVE_QUERY_ICC_LOCK_RETRY_COUNT; |
|
92 |
|
93 // Ril quirk to Send STK Profile Download |
|
94 let RILQUIRKS_SEND_STK_PROFILE_DOWNLOAD; |
|
95 |
|
96 // Ril quirk to attach data registration on demand. |
|
97 let RILQUIRKS_DATA_REGISTRATION_ON_DEMAND; |
|
98 |
|
99 function BufObject(aContext) { |
|
100 this.context = aContext; |
|
101 } |
|
102 BufObject.prototype = { |
|
103 context: null, |
|
104 |
|
105 mToken: 0, |
|
106 mTokenRequestMap: null, |
|
107 |
|
108 init: function() { |
|
109 this._init(); |
|
110 |
|
111 // This gets incremented each time we send out a parcel. |
|
112 this.mToken = 1; |
|
113 |
|
114 // Maps tokens we send out with requests to the request type, so that |
|
115 // when we get a response parcel back, we know what request it was for. |
|
116 this.mTokenRequestMap = new Map(); |
|
117 }, |
|
118 |
|
119 /** |
|
120 * Process one parcel. |
|
121 */ |
|
122 processParcel: function() { |
|
123 let response_type = this.readInt32(); |
|
124 |
|
125 let request_type, options; |
|
126 if (response_type == RESPONSE_TYPE_SOLICITED) { |
|
127 let token = this.readInt32(); |
|
128 let error = this.readInt32(); |
|
129 |
|
130 options = this.mTokenRequestMap.get(token); |
|
131 if (!options) { |
|
132 if (DEBUG) { |
|
133 this.context.debug("Suspicious uninvited request found: " + |
|
134 token + ". Ignored!"); |
|
135 } |
|
136 return; |
|
137 } |
|
138 |
|
139 this.mTokenRequestMap.delete(token); |
|
140 request_type = options.rilRequestType; |
|
141 |
|
142 options.rilRequestError = error; |
|
143 if (DEBUG) { |
|
144 this.context.debug("Solicited response for request type " + request_type + |
|
145 ", token " + token + ", error " + error); |
|
146 } |
|
147 } else if (response_type == RESPONSE_TYPE_UNSOLICITED) { |
|
148 request_type = this.readInt32(); |
|
149 if (DEBUG) { |
|
150 this.context.debug("Unsolicited response for request type " + request_type); |
|
151 } |
|
152 } else { |
|
153 if (DEBUG) { |
|
154 this.context.debug("Unknown response type: " + response_type); |
|
155 } |
|
156 return; |
|
157 } |
|
158 |
|
159 this.context.RIL.handleParcel(request_type, this.readAvailable, options); |
|
160 }, |
|
161 |
|
162 /** |
|
163 * Start a new outgoing parcel. |
|
164 * |
|
165 * @param type |
|
166 * Integer specifying the request type. |
|
167 * @param options [optional] |
|
168 * Object containing information about the request, e.g. the |
|
169 * original main thread message object that led to the RIL request. |
|
170 */ |
|
171 newParcel: function(type, options) { |
|
172 if (DEBUG) this.context.debug("New outgoing parcel of type " + type); |
|
173 |
|
174 // We're going to leave room for the parcel size at the beginning. |
|
175 this.outgoingIndex = this.PARCEL_SIZE_SIZE; |
|
176 this.writeInt32(type); |
|
177 this.writeInt32(this.mToken); |
|
178 |
|
179 if (!options) { |
|
180 options = {}; |
|
181 } |
|
182 options.rilRequestType = type; |
|
183 options.rilRequestError = null; |
|
184 this.mTokenRequestMap.set(this.mToken, options); |
|
185 this.mToken++; |
|
186 return this.mToken; |
|
187 }, |
|
188 |
|
189 simpleRequest: function(type, options) { |
|
190 this.newParcel(type, options); |
|
191 this.sendParcel(); |
|
192 }, |
|
193 |
|
194 onSendParcel: function(parcel) { |
|
195 postRILMessage(this.context.clientId, parcel); |
|
196 } |
|
197 }; |
|
198 |
|
199 (function() { |
|
200 let base = require("resource://gre/modules/workers/worker_buf.js").Buf; |
|
201 for (let p in base) { |
|
202 BufObject.prototype[p] = base[p]; |
|
203 } |
|
204 })(); |
|
205 |
|
206 /** |
|
207 * The RIL state machine. |
|
208 * |
|
209 * This object communicates with rild via parcels and with the main thread |
|
210 * via post messages. It maintains state about the radio, ICC, calls, etc. |
|
211 * and acts upon state changes accordingly. |
|
212 */ |
|
213 function RilObject(aContext) { |
|
214 this.context = aContext; |
|
215 |
|
216 this.currentCalls = {}; |
|
217 this.currentConference = {state: null, participants: {}}; |
|
218 this.currentDataCalls = {}; |
|
219 this._pendingSentSmsMap = {}; |
|
220 this.pendingNetworkType = {}; |
|
221 this._receivedSmsCbPagesMap = {}; |
|
222 |
|
223 // Init properties that are only initialized once. |
|
224 this.v5Legacy = RILQUIRKS_V5_LEGACY; |
|
225 this.cellBroadcastDisabled = RIL_CELLBROADCAST_DISABLED; |
|
226 this.clirMode = RIL_CLIR_MODE; |
|
227 } |
|
228 RilObject.prototype = { |
|
229 context: null, |
|
230 |
|
231 v5Legacy: null, |
|
232 |
|
233 /** |
|
234 * Valid calls. |
|
235 */ |
|
236 currentCalls: null, |
|
237 |
|
238 /** |
|
239 * Existing conference call and its participants. |
|
240 */ |
|
241 currentConference: null, |
|
242 |
|
243 /** |
|
244 * Existing data calls. |
|
245 */ |
|
246 currentDataCalls: null, |
|
247 |
|
248 /** |
|
249 * Outgoing messages waiting for SMS-STATUS-REPORT. |
|
250 */ |
|
251 _pendingSentSmsMap: null, |
|
252 |
|
253 /** |
|
254 * Index of the RIL_PREFERRED_NETWORK_TYPE_TO_GECKO. Its value should be |
|
255 * preserved over rild reset. |
|
256 */ |
|
257 preferredNetworkType: null, |
|
258 |
|
259 /** |
|
260 * Marker object. |
|
261 */ |
|
262 pendingNetworkType: null, |
|
263 |
|
264 /** |
|
265 * Global Cell Broadcast switch. |
|
266 */ |
|
267 cellBroadcastDisabled: false, |
|
268 |
|
269 /** |
|
270 * Global CLIR mode settings. |
|
271 */ |
|
272 clirMode: CLIR_DEFAULT, |
|
273 |
|
274 /** |
|
275 * Parsed Cell Broadcast search lists. |
|
276 * cellBroadcastConfigs.MMI should be preserved over rild reset. |
|
277 */ |
|
278 cellBroadcastConfigs: null, |
|
279 mergedCellBroadcastConfig: null, |
|
280 |
|
281 _receivedSmsCbPagesMap: null, |
|
282 |
|
283 initRILState: function() { |
|
284 /** |
|
285 * One of the RADIO_STATE_* constants. |
|
286 */ |
|
287 this.radioState = GECKO_RADIOSTATE_UNAVAILABLE; |
|
288 |
|
289 /** |
|
290 * True if we are on a CDMA phone. |
|
291 */ |
|
292 this._isCdma = false; |
|
293 |
|
294 /** |
|
295 * True if we are in emergency callback mode. |
|
296 */ |
|
297 this._isInEmergencyCbMode = false; |
|
298 |
|
299 /** |
|
300 * Set when radio is ready but radio tech is unknown. That is, we are |
|
301 * waiting for REQUEST_VOICE_RADIO_TECH |
|
302 */ |
|
303 this._waitingRadioTech = false; |
|
304 |
|
305 /** |
|
306 * ICC status. Keeps a reference of the data response to the |
|
307 * getICCStatus request. |
|
308 */ |
|
309 this.iccStatus = null; |
|
310 |
|
311 /** |
|
312 * Card state |
|
313 */ |
|
314 this.cardState = GECKO_CARDSTATE_UNINITIALIZED; |
|
315 |
|
316 /** |
|
317 * Strings |
|
318 */ |
|
319 this.IMEI = null; |
|
320 this.IMEISV = null; |
|
321 this.ESN = null; |
|
322 this.MEID = null; |
|
323 this.SMSC = null; |
|
324 |
|
325 /** |
|
326 * ICC information that is not exposed to Gaia. |
|
327 */ |
|
328 this.iccInfoPrivate = {}; |
|
329 |
|
330 /** |
|
331 * ICC information, such as MSISDN, MCC, MNC, SPN...etc. |
|
332 */ |
|
333 this.iccInfo = {}; |
|
334 |
|
335 /** |
|
336 * CDMA specific information. ex. CDMA Network ID, CDMA System ID... etc. |
|
337 */ |
|
338 this.cdmaHome = null; |
|
339 |
|
340 /** |
|
341 * Application identification for apps in ICC. |
|
342 */ |
|
343 this.aid = null; |
|
344 |
|
345 /** |
|
346 * Application type for apps in ICC. |
|
347 */ |
|
348 this.appType = null; |
|
349 |
|
350 this.networkSelectionMode = null; |
|
351 |
|
352 this.voiceRegistrationState = {}; |
|
353 this.dataRegistrationState = {}; |
|
354 |
|
355 /** |
|
356 * List of strings identifying the network operator. |
|
357 */ |
|
358 this.operator = null; |
|
359 |
|
360 /** |
|
361 * String containing the baseband version. |
|
362 */ |
|
363 this.basebandVersion = null; |
|
364 |
|
365 // Clean up this.currentCalls: rild might have restarted. |
|
366 for each (let currentCall in this.currentCalls) { |
|
367 delete this.currentCalls[currentCall.callIndex]; |
|
368 this._handleDisconnectedCall(currentCall); |
|
369 } |
|
370 |
|
371 // Deactivate this.currentDataCalls: rild might have restarted. |
|
372 for each (let datacall in this.currentDataCalls) { |
|
373 this.deactivateDataCall(datacall); |
|
374 } |
|
375 |
|
376 // Don't clean up this._pendingSentSmsMap |
|
377 // because on rild restart: we may continue with the pending segments. |
|
378 |
|
379 /** |
|
380 * Whether or not the multiple requests in requestNetworkInfo() are currently |
|
381 * being processed |
|
382 */ |
|
383 this._processingNetworkInfo = false; |
|
384 |
|
385 /** |
|
386 * Multiple requestNetworkInfo() in a row before finishing the first |
|
387 * request, hence we need to fire requestNetworkInfo() again after |
|
388 * gathering all necessary stuffs. This is to make sure that ril_worker |
|
389 * gets precise network information. |
|
390 */ |
|
391 this._needRepollNetworkInfo = false; |
|
392 |
|
393 /** |
|
394 * Pending messages to be send in batch from requestNetworkInfo() |
|
395 */ |
|
396 this._pendingNetworkInfo = {rilMessageType: "networkinfochanged"}; |
|
397 |
|
398 /** |
|
399 * USSD session flag. |
|
400 * Only one USSD session may exist at a time, and the session is assumed |
|
401 * to exist until: |
|
402 * a) There's a call to cancelUSSD() |
|
403 * b) The implementation sends a UNSOLICITED_ON_USSD with a type code |
|
404 * of "0" (USSD-Notify/no further action) or "2" (session terminated) |
|
405 */ |
|
406 this._ussdSession = null; |
|
407 |
|
408 /** |
|
409 * Regular expresion to parse MMI codes. |
|
410 */ |
|
411 this._mmiRegExp = null; |
|
412 |
|
413 /** |
|
414 * Cell Broadcast Search Lists. |
|
415 */ |
|
416 let cbmmi = this.cellBroadcastConfigs && this.cellBroadcastConfigs.MMI; |
|
417 this.cellBroadcastConfigs = { |
|
418 MMI: cbmmi || null |
|
419 }; |
|
420 this.mergedCellBroadcastConfig = null; |
|
421 }, |
|
422 |
|
423 /** |
|
424 * Parse an integer from a string, falling back to a default value |
|
425 * if the the provided value is not a string or does not contain a valid |
|
426 * number. |
|
427 * |
|
428 * @param string |
|
429 * String to be parsed. |
|
430 * @param defaultValue [optional] |
|
431 * Default value to be used. |
|
432 * @param radix [optional] |
|
433 * A number that represents the numeral system to be used. Default 10. |
|
434 */ |
|
435 parseInt: function(string, defaultValue, radix) { |
|
436 let number = parseInt(string, radix || 10); |
|
437 if (!isNaN(number)) { |
|
438 return number; |
|
439 } |
|
440 if (defaultValue === undefined) { |
|
441 defaultValue = null; |
|
442 } |
|
443 return defaultValue; |
|
444 }, |
|
445 |
|
446 |
|
447 /** |
|
448 * Outgoing requests to the RIL. These can be triggered from the |
|
449 * main thread via messages that look like this: |
|
450 * |
|
451 * {rilMessageType: "methodName", |
|
452 * extra: "parameters", |
|
453 * go: "here"} |
|
454 * |
|
455 * So if one of the following methods takes arguments, it takes only one, |
|
456 * an object, which then contains all of the parameters as attributes. |
|
457 * The "@param" documentation is to be interpreted accordingly. |
|
458 */ |
|
459 |
|
460 /** |
|
461 * Retrieve the ICC's status. |
|
462 */ |
|
463 getICCStatus: function() { |
|
464 this.context.Buf.simpleRequest(REQUEST_GET_SIM_STATUS); |
|
465 }, |
|
466 |
|
467 /** |
|
468 * Helper function for unlocking ICC locks. |
|
469 */ |
|
470 iccUnlockCardLock: function(options) { |
|
471 switch (options.lockType) { |
|
472 case GECKO_CARDLOCK_PIN: |
|
473 this.enterICCPIN(options); |
|
474 break; |
|
475 case GECKO_CARDLOCK_PIN2: |
|
476 this.enterICCPIN2(options); |
|
477 break; |
|
478 case GECKO_CARDLOCK_PUK: |
|
479 this.enterICCPUK(options); |
|
480 break; |
|
481 case GECKO_CARDLOCK_PUK2: |
|
482 this.enterICCPUK2(options); |
|
483 break; |
|
484 case GECKO_CARDLOCK_NCK: |
|
485 case GECKO_CARDLOCK_NCK1: |
|
486 case GECKO_CARDLOCK_NCK2: |
|
487 case GECKO_CARDLOCK_HNCK: |
|
488 case GECKO_CARDLOCK_CCK: |
|
489 case GECKO_CARDLOCK_SPCK: |
|
490 case GECKO_CARDLOCK_RCCK: // Fall through. |
|
491 case GECKO_CARDLOCK_RSPCK: { |
|
492 let type = GECKO_PERSO_LOCK_TO_CARD_PERSO_LOCK[options.lockType]; |
|
493 this.enterDepersonalization(type, options.pin, options); |
|
494 break; |
|
495 } |
|
496 case GECKO_CARDLOCK_NCK_PUK: |
|
497 case GECKO_CARDLOCK_NCK1_PUK: |
|
498 case GECKO_CARDLOCK_NCK2_PUK: |
|
499 case GECKO_CARDLOCK_HNCK_PUK: |
|
500 case GECKO_CARDLOCK_CCK_PUK: |
|
501 case GECKO_CARDLOCK_SPCK_PUK: |
|
502 case GECKO_CARDLOCK_RCCK_PUK: // Fall through. |
|
503 case GECKO_CARDLOCK_RSPCK_PUK: { |
|
504 let type = GECKO_PERSO_LOCK_TO_CARD_PERSO_LOCK[options.lockType]; |
|
505 this.enterDepersonalization(type, options.puk, options); |
|
506 break; |
|
507 } |
|
508 default: |
|
509 options.errorMsg = "Unsupported Card Lock."; |
|
510 options.success = false; |
|
511 this.sendChromeMessage(options); |
|
512 } |
|
513 }, |
|
514 |
|
515 /** |
|
516 * Enter a PIN to unlock the ICC. |
|
517 * |
|
518 * @param pin |
|
519 * String containing the PIN. |
|
520 * @param [optional] aid |
|
521 * AID value. |
|
522 */ |
|
523 enterICCPIN: function(options) { |
|
524 let Buf = this.context.Buf; |
|
525 Buf.newParcel(REQUEST_ENTER_SIM_PIN, options); |
|
526 Buf.writeInt32(this.v5Legacy ? 1 : 2); |
|
527 Buf.writeString(options.pin); |
|
528 if (!this.v5Legacy) { |
|
529 Buf.writeString(options.aid || this.aid); |
|
530 } |
|
531 Buf.sendParcel(); |
|
532 }, |
|
533 |
|
534 /** |
|
535 * Enter a PIN2 to unlock the ICC. |
|
536 * |
|
537 * @param pin |
|
538 * String containing the PIN2. |
|
539 * @param [optional] aid |
|
540 * AID value. |
|
541 */ |
|
542 enterICCPIN2: function(options) { |
|
543 let Buf = this.context.Buf; |
|
544 Buf.newParcel(REQUEST_ENTER_SIM_PIN2, options); |
|
545 Buf.writeInt32(this.v5Legacy ? 1 : 2); |
|
546 Buf.writeString(options.pin); |
|
547 if (!this.v5Legacy) { |
|
548 Buf.writeString(options.aid || this.aid); |
|
549 } |
|
550 Buf.sendParcel(); |
|
551 }, |
|
552 |
|
553 /** |
|
554 * Requests a network personalization be deactivated. |
|
555 * |
|
556 * @param type |
|
557 * Integer indicating the network personalization be deactivated. |
|
558 * @param password |
|
559 * String containing the password. |
|
560 */ |
|
561 enterDepersonalization: function(type, password, options) { |
|
562 let Buf = this.context.Buf; |
|
563 Buf.newParcel(REQUEST_ENTER_NETWORK_DEPERSONALIZATION_CODE, options); |
|
564 Buf.writeInt32(type); |
|
565 Buf.writeString(password); |
|
566 Buf.sendParcel(); |
|
567 }, |
|
568 |
|
569 /** |
|
570 * Helper function for changing ICC locks. |
|
571 */ |
|
572 iccSetCardLock: function(options) { |
|
573 if (options.newPin !== undefined) { // Change PIN lock. |
|
574 switch (options.lockType) { |
|
575 case GECKO_CARDLOCK_PIN: |
|
576 this.changeICCPIN(options); |
|
577 break; |
|
578 case GECKO_CARDLOCK_PIN2: |
|
579 this.changeICCPIN2(options); |
|
580 break; |
|
581 default: |
|
582 options.errorMsg = "Unsupported Card Lock."; |
|
583 options.success = false; |
|
584 this.sendChromeMessage(options); |
|
585 } |
|
586 } else { // Enable/Disable lock. |
|
587 switch (options.lockType) { |
|
588 case GECKO_CARDLOCK_PIN: |
|
589 options.facility = ICC_CB_FACILITY_SIM; |
|
590 options.password = options.pin; |
|
591 break; |
|
592 case GECKO_CARDLOCK_FDN: |
|
593 options.facility = ICC_CB_FACILITY_FDN; |
|
594 options.password = options.pin2; |
|
595 break; |
|
596 default: |
|
597 options.errorMsg = "Unsupported Card Lock."; |
|
598 options.success = false; |
|
599 this.sendChromeMessage(options); |
|
600 return; |
|
601 } |
|
602 options.enabled = options.enabled; |
|
603 options.serviceClass = ICC_SERVICE_CLASS_VOICE | |
|
604 ICC_SERVICE_CLASS_DATA | |
|
605 ICC_SERVICE_CLASS_FAX; |
|
606 this.setICCFacilityLock(options); |
|
607 } |
|
608 }, |
|
609 |
|
610 /** |
|
611 * Change the current ICC PIN number. |
|
612 * |
|
613 * @param pin |
|
614 * String containing the old PIN value |
|
615 * @param newPin |
|
616 * String containing the new PIN value |
|
617 * @param [optional] aid |
|
618 * AID value. |
|
619 */ |
|
620 changeICCPIN: function(options) { |
|
621 let Buf = this.context.Buf; |
|
622 Buf.newParcel(REQUEST_CHANGE_SIM_PIN, options); |
|
623 Buf.writeInt32(this.v5Legacy ? 2 : 3); |
|
624 Buf.writeString(options.pin); |
|
625 Buf.writeString(options.newPin); |
|
626 if (!this.v5Legacy) { |
|
627 Buf.writeString(options.aid || this.aid); |
|
628 } |
|
629 Buf.sendParcel(); |
|
630 }, |
|
631 |
|
632 /** |
|
633 * Change the current ICC PIN2 number. |
|
634 * |
|
635 * @param pin |
|
636 * String containing the old PIN2 value |
|
637 * @param newPin |
|
638 * String containing the new PIN2 value |
|
639 * @param [optional] aid |
|
640 * AID value. |
|
641 */ |
|
642 changeICCPIN2: function(options) { |
|
643 let Buf = this.context.Buf; |
|
644 Buf.newParcel(REQUEST_CHANGE_SIM_PIN2, options); |
|
645 Buf.writeInt32(this.v5Legacy ? 2 : 3); |
|
646 Buf.writeString(options.pin); |
|
647 Buf.writeString(options.newPin); |
|
648 if (!this.v5Legacy) { |
|
649 Buf.writeString(options.aid || this.aid); |
|
650 } |
|
651 Buf.sendParcel(); |
|
652 }, |
|
653 /** |
|
654 * Supplies ICC PUK and a new PIN to unlock the ICC. |
|
655 * |
|
656 * @param puk |
|
657 * String containing the PUK value. |
|
658 * @param newPin |
|
659 * String containing the new PIN value. |
|
660 * @param [optional] aid |
|
661 * AID value. |
|
662 */ |
|
663 enterICCPUK: function(options) { |
|
664 let Buf = this.context.Buf; |
|
665 Buf.newParcel(REQUEST_ENTER_SIM_PUK, options); |
|
666 Buf.writeInt32(this.v5Legacy ? 2 : 3); |
|
667 Buf.writeString(options.puk); |
|
668 Buf.writeString(options.newPin); |
|
669 if (!this.v5Legacy) { |
|
670 Buf.writeString(options.aid || this.aid); |
|
671 } |
|
672 Buf.sendParcel(); |
|
673 }, |
|
674 |
|
675 /** |
|
676 * Supplies ICC PUK2 and a new PIN2 to unlock the ICC. |
|
677 * |
|
678 * @param puk |
|
679 * String containing the PUK2 value. |
|
680 * @param newPin |
|
681 * String containing the new PIN2 value. |
|
682 * @param [optional] aid |
|
683 * AID value. |
|
684 */ |
|
685 enterICCPUK2: function(options) { |
|
686 let Buf = this.context.Buf; |
|
687 Buf.newParcel(REQUEST_ENTER_SIM_PUK2, options); |
|
688 Buf.writeInt32(this.v5Legacy ? 2 : 3); |
|
689 Buf.writeString(options.puk); |
|
690 Buf.writeString(options.newPin); |
|
691 if (!this.v5Legacy) { |
|
692 Buf.writeString(options.aid || this.aid); |
|
693 } |
|
694 Buf.sendParcel(); |
|
695 }, |
|
696 |
|
697 /** |
|
698 * Helper function for fetching the state of ICC locks. |
|
699 */ |
|
700 iccGetCardLockState: function(options) { |
|
701 switch (options.lockType) { |
|
702 case GECKO_CARDLOCK_PIN: |
|
703 options.facility = ICC_CB_FACILITY_SIM; |
|
704 break; |
|
705 case GECKO_CARDLOCK_FDN: |
|
706 options.facility = ICC_CB_FACILITY_FDN; |
|
707 break; |
|
708 default: |
|
709 options.errorMsg = "Unsupported Card Lock."; |
|
710 options.success = false; |
|
711 this.sendChromeMessage(options); |
|
712 return; |
|
713 } |
|
714 |
|
715 options.password = ""; // For query no need to provide pin. |
|
716 options.serviceClass = ICC_SERVICE_CLASS_VOICE | |
|
717 ICC_SERVICE_CLASS_DATA | |
|
718 ICC_SERVICE_CLASS_FAX; |
|
719 this.queryICCFacilityLock(options); |
|
720 }, |
|
721 |
|
722 /** |
|
723 * Helper function for fetching the number of unlock retries of ICC locks. |
|
724 * |
|
725 * We only query the retry count when we're on the emulator. The phones do |
|
726 * not support the request id and their rild doesn't return an error. |
|
727 */ |
|
728 iccGetCardLockRetryCount: function(options) { |
|
729 var selCode = { |
|
730 pin: ICC_SEL_CODE_SIM_PIN, |
|
731 puk: ICC_SEL_CODE_SIM_PUK, |
|
732 pin2: ICC_SEL_CODE_SIM_PIN2, |
|
733 puk2: ICC_SEL_CODE_SIM_PUK2, |
|
734 nck: ICC_SEL_CODE_PH_NET_PIN, |
|
735 cck: ICC_SEL_CODE_PH_CORP_PIN, |
|
736 spck: ICC_SEL_CODE_PH_SP_PIN |
|
737 }; |
|
738 |
|
739 if (typeof(selCode[options.lockType]) === 'undefined') { |
|
740 /* unknown lock type */ |
|
741 options.errorMsg = GECKO_ERROR_GENERIC_FAILURE; |
|
742 options.success = false; |
|
743 this.sendChromeMessage(options); |
|
744 return; |
|
745 } |
|
746 |
|
747 if (RILQUIRKS_HAVE_QUERY_ICC_LOCK_RETRY_COUNT) { |
|
748 /* Only the emulator supports this request, ... */ |
|
749 options.selCode = selCode[options.lockType]; |
|
750 this.queryICCLockRetryCount(options); |
|
751 } else { |
|
752 /* ... while the phones do not. */ |
|
753 options.errorMsg = GECKO_ERROR_REQUEST_NOT_SUPPORTED; |
|
754 options.success = false; |
|
755 this.sendChromeMessage(options); |
|
756 } |
|
757 }, |
|
758 |
|
759 /** |
|
760 * Query ICC lock retry count. |
|
761 * |
|
762 * @param selCode |
|
763 * One of ICC_SEL_CODE_*. |
|
764 * @param serviceClass |
|
765 * One of ICC_SERVICE_CLASS_*. |
|
766 */ |
|
767 queryICCLockRetryCount: function(options) { |
|
768 let Buf = this.context.Buf; |
|
769 Buf.newParcel(REQUEST_GET_UNLOCK_RETRY_COUNT, options); |
|
770 Buf.writeInt32(1); |
|
771 Buf.writeString(options.selCode); |
|
772 Buf.sendParcel(); |
|
773 }, |
|
774 |
|
775 /** |
|
776 * Query ICC facility lock. |
|
777 * |
|
778 * @param facility |
|
779 * One of ICC_CB_FACILITY_*. |
|
780 * @param password |
|
781 * Password for the facility, or "" if not required. |
|
782 * @param serviceClass |
|
783 * One of ICC_SERVICE_CLASS_*. |
|
784 * @param [optional] aid |
|
785 * AID value. |
|
786 */ |
|
787 queryICCFacilityLock: function(options) { |
|
788 let Buf = this.context.Buf; |
|
789 Buf.newParcel(REQUEST_QUERY_FACILITY_LOCK, options); |
|
790 Buf.writeInt32(this.v5Legacy ? 3 : 4); |
|
791 Buf.writeString(options.facility); |
|
792 Buf.writeString(options.password); |
|
793 Buf.writeString(options.serviceClass.toString()); |
|
794 if (!this.v5Legacy) { |
|
795 Buf.writeString(options.aid || this.aid); |
|
796 } |
|
797 Buf.sendParcel(); |
|
798 }, |
|
799 |
|
800 /** |
|
801 * Set ICC facility lock. |
|
802 * |
|
803 * @param facility |
|
804 * One of ICC_CB_FACILITY_*. |
|
805 * @param enabled |
|
806 * true to enable, false to disable. |
|
807 * @param password |
|
808 * Password for the facility, or "" if not required. |
|
809 * @param serviceClass |
|
810 * One of ICC_SERVICE_CLASS_*. |
|
811 * @param [optional] aid |
|
812 * AID value. |
|
813 */ |
|
814 setICCFacilityLock: function(options) { |
|
815 let Buf = this.context.Buf; |
|
816 Buf.newParcel(REQUEST_SET_FACILITY_LOCK, options); |
|
817 Buf.writeInt32(this.v5Legacy ? 4 : 5); |
|
818 Buf.writeString(options.facility); |
|
819 Buf.writeString(options.enabled ? "1" : "0"); |
|
820 Buf.writeString(options.password); |
|
821 Buf.writeString(options.serviceClass.toString()); |
|
822 if (!this.v5Legacy) { |
|
823 Buf.writeString(options.aid || this.aid); |
|
824 } |
|
825 Buf.sendParcel(); |
|
826 }, |
|
827 |
|
828 /** |
|
829 * Request an ICC I/O operation. |
|
830 * |
|
831 * See TS 27.007 "restricted SIM" operation, "AT Command +CRSM". |
|
832 * The sequence is in the same order as how libril reads this parcel, |
|
833 * see the struct RIL_SIM_IO_v5 or RIL_SIM_IO_v6 defined in ril.h |
|
834 * |
|
835 * @param command |
|
836 * The I/O command, one of the ICC_COMMAND_* constants. |
|
837 * @param fileId |
|
838 * The file to operate on, one of the ICC_EF_* constants. |
|
839 * @param pathId |
|
840 * String type, check the 'pathid' parameter from TS 27.007 +CRSM. |
|
841 * @param p1, p2, p3 |
|
842 * Arbitrary integer parameters for the command. |
|
843 * @param [optional] dataWriter |
|
844 * The function for writing string parameter for the ICC_COMMAND_UPDATE_RECORD. |
|
845 * @param [optional] pin2 |
|
846 * String containing the PIN2. |
|
847 * @param [optional] aid |
|
848 * AID value. |
|
849 */ |
|
850 iccIO: function(options) { |
|
851 let Buf = this.context.Buf; |
|
852 Buf.newParcel(REQUEST_SIM_IO, options); |
|
853 Buf.writeInt32(options.command); |
|
854 Buf.writeInt32(options.fileId); |
|
855 Buf.writeString(options.pathId); |
|
856 Buf.writeInt32(options.p1); |
|
857 Buf.writeInt32(options.p2); |
|
858 Buf.writeInt32(options.p3); |
|
859 |
|
860 // Write data. |
|
861 if (options.command == ICC_COMMAND_UPDATE_RECORD && |
|
862 options.dataWriter) { |
|
863 options.dataWriter(options.p3); |
|
864 } else { |
|
865 Buf.writeString(null); |
|
866 } |
|
867 |
|
868 // Write pin2. |
|
869 if (options.command == ICC_COMMAND_UPDATE_RECORD && |
|
870 options.pin2) { |
|
871 Buf.writeString(options.pin2); |
|
872 } else { |
|
873 Buf.writeString(null); |
|
874 } |
|
875 |
|
876 if (!this.v5Legacy) { |
|
877 Buf.writeString(options.aid || this.aid); |
|
878 } |
|
879 Buf.sendParcel(); |
|
880 }, |
|
881 |
|
882 /** |
|
883 * Get IMSI. |
|
884 * |
|
885 * @param [optional] aid |
|
886 * AID value. |
|
887 */ |
|
888 getIMSI: function(aid) { |
|
889 let Buf = this.context.Buf; |
|
890 if (this.v5Legacy) { |
|
891 Buf.simpleRequest(REQUEST_GET_IMSI); |
|
892 return; |
|
893 } |
|
894 Buf.newParcel(REQUEST_GET_IMSI); |
|
895 Buf.writeInt32(1); |
|
896 Buf.writeString(aid || this.aid); |
|
897 Buf.sendParcel(); |
|
898 }, |
|
899 |
|
900 /** |
|
901 * Read UICC Phonebook contacts. |
|
902 * |
|
903 * @param contactType |
|
904 * "adn" or "fdn". |
|
905 * @param requestId |
|
906 * Request id from RadioInterfaceLayer. |
|
907 */ |
|
908 readICCContacts: function(options) { |
|
909 if (!this.appType) { |
|
910 options.errorMsg = CONTACT_ERR_REQUEST_NOT_SUPPORTED; |
|
911 this.sendChromeMessage(options); |
|
912 return; |
|
913 } |
|
914 |
|
915 this.context.ICCContactHelper.readICCContacts( |
|
916 this.appType, |
|
917 options.contactType, |
|
918 function onsuccess(contacts) { |
|
919 for (let i = 0; i < contacts.length; i++) { |
|
920 let contact = contacts[i]; |
|
921 let pbrIndex = contact.pbrIndex || 0; |
|
922 let recordIndex = pbrIndex * ICC_MAX_LINEAR_FIXED_RECORDS + contact.recordId; |
|
923 contact.contactId = this.iccInfo.iccid + recordIndex; |
|
924 } |
|
925 // Reuse 'options' to get 'requestId' and 'contactType'. |
|
926 options.contacts = contacts; |
|
927 this.sendChromeMessage(options); |
|
928 }.bind(this), |
|
929 function onerror(errorMsg) { |
|
930 options.errorMsg = errorMsg; |
|
931 this.sendChromeMessage(options); |
|
932 }.bind(this)); |
|
933 }, |
|
934 |
|
935 /** |
|
936 * Update UICC Phonebook. |
|
937 * |
|
938 * @param contactType "adn" or "fdn". |
|
939 * @param contact The contact will be updated. |
|
940 * @param pin2 PIN2 is required for updating FDN. |
|
941 * @param requestId Request id from RadioInterfaceLayer. |
|
942 */ |
|
943 updateICCContact: function(options) { |
|
944 let onsuccess = function onsuccess() { |
|
945 let recordIndex = |
|
946 contact.pbrIndex * ICC_MAX_LINEAR_FIXED_RECORDS + contact.recordId; |
|
947 contact.contactId = this.iccInfo.iccid + recordIndex; |
|
948 // Reuse 'options' to get 'requestId' and 'contactType'. |
|
949 this.sendChromeMessage(options); |
|
950 }.bind(this); |
|
951 |
|
952 let onerror = function onerror(errorMsg) { |
|
953 options.errorMsg = errorMsg; |
|
954 this.sendChromeMessage(options); |
|
955 }.bind(this); |
|
956 |
|
957 if (!this.appType || !options.contact) { |
|
958 onerror(CONTACT_ERR_REQUEST_NOT_SUPPORTED ); |
|
959 return; |
|
960 } |
|
961 |
|
962 let contact = options.contact; |
|
963 let iccid = this.iccInfo.iccid; |
|
964 let isValidRecordId = false; |
|
965 if (typeof contact.contactId === "string" && |
|
966 contact.contactId.startsWith(iccid)) { |
|
967 let recordIndex = contact.contactId.substring(iccid.length); |
|
968 contact.pbrIndex = Math.floor(recordIndex / ICC_MAX_LINEAR_FIXED_RECORDS); |
|
969 contact.recordId = recordIndex % ICC_MAX_LINEAR_FIXED_RECORDS; |
|
970 isValidRecordId = contact.recordId > 0 && contact.recordId < 0xff; |
|
971 } |
|
972 |
|
973 if (DEBUG) { |
|
974 this.context.debug("Update ICC Contact " + JSON.stringify(contact)); |
|
975 } |
|
976 |
|
977 let ICCContactHelper = this.context.ICCContactHelper; |
|
978 // If contact has 'recordId' property, updates corresponding record. |
|
979 // If not, inserts the contact into a free record. |
|
980 if (isValidRecordId) { |
|
981 ICCContactHelper.updateICCContact( |
|
982 this.appType, options.contactType, contact, options.pin2, onsuccess, onerror); |
|
983 } else { |
|
984 ICCContactHelper.addICCContact( |
|
985 this.appType, options.contactType, contact, options.pin2, onsuccess, onerror); |
|
986 } |
|
987 }, |
|
988 |
|
989 /** |
|
990 * Request the phone's radio to be enabled or disabled. |
|
991 * |
|
992 * @param enabled |
|
993 * Boolean indicating the desired state. |
|
994 */ |
|
995 setRadioEnabled: function(options) { |
|
996 let Buf = this.context.Buf; |
|
997 Buf.newParcel(REQUEST_RADIO_POWER, options); |
|
998 Buf.writeInt32(1); |
|
999 Buf.writeInt32(options.enabled ? 1 : 0); |
|
1000 Buf.sendParcel(); |
|
1001 }, |
|
1002 |
|
1003 /** |
|
1004 * Query call waiting status via MMI. |
|
1005 */ |
|
1006 _handleQueryMMICallWaiting: function(options) { |
|
1007 let Buf = this.context.Buf; |
|
1008 |
|
1009 function callback(options) { |
|
1010 options.length = Buf.readInt32(); |
|
1011 options.enabled = (Buf.readInt32() === 1); |
|
1012 let services = Buf.readInt32(); |
|
1013 if (options.enabled) { |
|
1014 options.statusMessage = MMI_SM_KS_SERVICE_ENABLED_FOR; |
|
1015 let serviceClass = []; |
|
1016 for (let serviceClassMask = 1; |
|
1017 serviceClassMask <= ICC_SERVICE_CLASS_MAX; |
|
1018 serviceClassMask <<= 1) { |
|
1019 if ((serviceClassMask & services) !== 0) { |
|
1020 serviceClass.push(MMI_KS_SERVICE_CLASS_MAPPING[serviceClassMask]); |
|
1021 } |
|
1022 } |
|
1023 options.additionalInformation = serviceClass; |
|
1024 } else { |
|
1025 options.statusMessage = MMI_SM_KS_SERVICE_DISABLED; |
|
1026 } |
|
1027 |
|
1028 // Prevent DataCloneError when sending chrome messages. |
|
1029 delete options.callback; |
|
1030 this.sendChromeMessage(options); |
|
1031 } |
|
1032 |
|
1033 options.callback = callback; |
|
1034 this.queryCallWaiting(options); |
|
1035 }, |
|
1036 |
|
1037 /** |
|
1038 * Set call waiting status via MMI. |
|
1039 */ |
|
1040 _handleSetMMICallWaiting: function(options) { |
|
1041 function callback(options) { |
|
1042 if (options.enabled) { |
|
1043 options.statusMessage = MMI_SM_KS_SERVICE_ENABLED; |
|
1044 } else { |
|
1045 options.statusMessage = MMI_SM_KS_SERVICE_DISABLED; |
|
1046 } |
|
1047 |
|
1048 // Prevent DataCloneError when sending chrome messages. |
|
1049 delete options.callback; |
|
1050 this.sendChromeMessage(options); |
|
1051 } |
|
1052 |
|
1053 options.callback = callback; |
|
1054 this.setCallWaiting(options); |
|
1055 }, |
|
1056 |
|
1057 /** |
|
1058 * Query call waiting status. |
|
1059 * |
|
1060 */ |
|
1061 queryCallWaiting: function(options) { |
|
1062 let Buf = this.context.Buf; |
|
1063 Buf.newParcel(REQUEST_QUERY_CALL_WAITING, options); |
|
1064 Buf.writeInt32(1); |
|
1065 // As per 3GPP TS 24.083, section 1.6 UE doesn't need to send service |
|
1066 // class parameter in call waiting interrogation to network |
|
1067 Buf.writeInt32(ICC_SERVICE_CLASS_NONE); |
|
1068 Buf.sendParcel(); |
|
1069 }, |
|
1070 |
|
1071 /** |
|
1072 * Set call waiting status. |
|
1073 * |
|
1074 * @param on |
|
1075 * Boolean indicating the desired waiting status. |
|
1076 */ |
|
1077 setCallWaiting: function(options) { |
|
1078 let Buf = this.context.Buf; |
|
1079 Buf.newParcel(REQUEST_SET_CALL_WAITING, options); |
|
1080 Buf.writeInt32(2); |
|
1081 Buf.writeInt32(options.enabled ? 1 : 0); |
|
1082 Buf.writeInt32(options.serviceClass !== undefined ? |
|
1083 options.serviceClass : ICC_SERVICE_CLASS_VOICE); |
|
1084 Buf.sendParcel(); |
|
1085 }, |
|
1086 |
|
1087 /** |
|
1088 * Queries current CLIP status. |
|
1089 * |
|
1090 * (MMI request for code "*#30#") |
|
1091 * |
|
1092 */ |
|
1093 queryCLIP: function(options) { |
|
1094 this.context.Buf.simpleRequest(REQUEST_QUERY_CLIP, options); |
|
1095 }, |
|
1096 |
|
1097 /** |
|
1098 * Queries current CLIR status. |
|
1099 * |
|
1100 */ |
|
1101 getCLIR: function(options) { |
|
1102 this.context.Buf.simpleRequest(REQUEST_GET_CLIR, options); |
|
1103 }, |
|
1104 |
|
1105 /** |
|
1106 * Enables or disables the presentation of the calling line identity (CLI) to |
|
1107 * the called party when originating a call. |
|
1108 * |
|
1109 * @param options.clirMode |
|
1110 * Is one of the CLIR_* constants in |
|
1111 * nsIDOMMozMobileConnection interface. |
|
1112 */ |
|
1113 setCLIR: function(options) { |
|
1114 if (options) { |
|
1115 this.clirMode = options.clirMode; |
|
1116 } |
|
1117 let Buf = this.context.Buf; |
|
1118 Buf.newParcel(REQUEST_SET_CLIR, options); |
|
1119 Buf.writeInt32(1); |
|
1120 Buf.writeInt32(this.clirMode); |
|
1121 Buf.sendParcel(); |
|
1122 }, |
|
1123 |
|
1124 /** |
|
1125 * Set screen state. |
|
1126 * |
|
1127 * @param on |
|
1128 * Boolean indicating whether the screen should be on or off. |
|
1129 */ |
|
1130 setScreenState: function(options) { |
|
1131 let Buf = this.context.Buf; |
|
1132 Buf.newParcel(REQUEST_SCREEN_STATE); |
|
1133 Buf.writeInt32(1); |
|
1134 Buf.writeInt32(options.on ? 1 : 0); |
|
1135 Buf.sendParcel(); |
|
1136 }, |
|
1137 |
|
1138 getVoiceRegistrationState: function() { |
|
1139 this.context.Buf.simpleRequest(REQUEST_VOICE_REGISTRATION_STATE); |
|
1140 }, |
|
1141 |
|
1142 getVoiceRadioTechnology: function() { |
|
1143 this.context.Buf.simpleRequest(REQUEST_VOICE_RADIO_TECH); |
|
1144 }, |
|
1145 |
|
1146 getDataRegistrationState: function() { |
|
1147 this.context.Buf.simpleRequest(REQUEST_DATA_REGISTRATION_STATE); |
|
1148 }, |
|
1149 |
|
1150 getOperator: function() { |
|
1151 this.context.Buf.simpleRequest(REQUEST_OPERATOR); |
|
1152 }, |
|
1153 |
|
1154 /** |
|
1155 * Set the preferred network type. |
|
1156 * |
|
1157 * @param options An object contains a valid index of |
|
1158 * RIL_PREFERRED_NETWORK_TYPE_TO_GECKO as its `networkType` |
|
1159 * attribute, or undefined to set current preferred network |
|
1160 * type. |
|
1161 */ |
|
1162 setPreferredNetworkType: function(options) { |
|
1163 if (options) { |
|
1164 this.preferredNetworkType = options.networkType; |
|
1165 } |
|
1166 if (this.preferredNetworkType == null) { |
|
1167 return; |
|
1168 } |
|
1169 |
|
1170 let Buf = this.context.Buf; |
|
1171 Buf.newParcel(REQUEST_SET_PREFERRED_NETWORK_TYPE, options); |
|
1172 Buf.writeInt32(1); |
|
1173 Buf.writeInt32(this.preferredNetworkType); |
|
1174 Buf.sendParcel(); |
|
1175 }, |
|
1176 |
|
1177 /** |
|
1178 * Get the preferred network type. |
|
1179 */ |
|
1180 getPreferredNetworkType: function(options) { |
|
1181 this.context.Buf.simpleRequest(REQUEST_GET_PREFERRED_NETWORK_TYPE, options); |
|
1182 }, |
|
1183 |
|
1184 /** |
|
1185 * Request various states about the network. |
|
1186 */ |
|
1187 requestNetworkInfo: function() { |
|
1188 if (this._processingNetworkInfo) { |
|
1189 if (DEBUG) { |
|
1190 this.context.debug("Network info requested, but we're already " + |
|
1191 "requesting network info."); |
|
1192 } |
|
1193 this._needRepollNetworkInfo = true; |
|
1194 return; |
|
1195 } |
|
1196 |
|
1197 if (DEBUG) this.context.debug("Requesting network info"); |
|
1198 |
|
1199 this._processingNetworkInfo = true; |
|
1200 this.getVoiceRegistrationState(); |
|
1201 this.getDataRegistrationState(); //TODO only GSM |
|
1202 this.getOperator(); |
|
1203 this.getNetworkSelectionMode(); |
|
1204 this.getSignalStrength(); |
|
1205 }, |
|
1206 |
|
1207 /** |
|
1208 * Get the available networks |
|
1209 */ |
|
1210 getAvailableNetworks: function(options) { |
|
1211 if (DEBUG) this.context.debug("Getting available networks"); |
|
1212 let Buf = this.context.Buf; |
|
1213 Buf.newParcel(REQUEST_QUERY_AVAILABLE_NETWORKS, options); |
|
1214 Buf.sendParcel(); |
|
1215 }, |
|
1216 |
|
1217 /** |
|
1218 * Request the radio's network selection mode |
|
1219 */ |
|
1220 getNetworkSelectionMode: function() { |
|
1221 if (DEBUG) this.context.debug("Getting network selection mode"); |
|
1222 this.context.Buf.simpleRequest(REQUEST_QUERY_NETWORK_SELECTION_MODE); |
|
1223 }, |
|
1224 |
|
1225 /** |
|
1226 * Tell the radio to automatically choose a voice/data network |
|
1227 */ |
|
1228 selectNetworkAuto: function(options) { |
|
1229 if (DEBUG) this.context.debug("Setting automatic network selection"); |
|
1230 this.context.Buf.simpleRequest(REQUEST_SET_NETWORK_SELECTION_AUTOMATIC, options); |
|
1231 }, |
|
1232 |
|
1233 /** |
|
1234 * Set the roaming preference mode |
|
1235 */ |
|
1236 setRoamingPreference: function(options) { |
|
1237 let roamingMode = CDMA_ROAMING_PREFERENCE_TO_GECKO.indexOf(options.mode); |
|
1238 |
|
1239 if (roamingMode === -1) { |
|
1240 options.errorMsg = GECKO_ERROR_INVALID_PARAMETER; |
|
1241 this.sendChromeMessage(options); |
|
1242 return; |
|
1243 } |
|
1244 |
|
1245 let Buf = this.context.Buf; |
|
1246 Buf.newParcel(REQUEST_CDMA_SET_ROAMING_PREFERENCE, options); |
|
1247 Buf.writeInt32(1); |
|
1248 Buf.writeInt32(roamingMode); |
|
1249 Buf.sendParcel(); |
|
1250 }, |
|
1251 |
|
1252 /** |
|
1253 * Get the roaming preference mode |
|
1254 */ |
|
1255 queryRoamingPreference: function(options) { |
|
1256 this.context.Buf.simpleRequest(REQUEST_CDMA_QUERY_ROAMING_PREFERENCE, options); |
|
1257 }, |
|
1258 |
|
1259 /** |
|
1260 * Set the voice privacy mode |
|
1261 */ |
|
1262 setVoicePrivacyMode: function(options) { |
|
1263 let Buf = this.context.Buf; |
|
1264 Buf.newParcel(REQUEST_CDMA_SET_PREFERRED_VOICE_PRIVACY_MODE, options); |
|
1265 Buf.writeInt32(1); |
|
1266 Buf.writeInt32(options.enabled ? 1 : 0); |
|
1267 Buf.sendParcel(); |
|
1268 }, |
|
1269 |
|
1270 /** |
|
1271 * Get the voice privacy mode |
|
1272 */ |
|
1273 queryVoicePrivacyMode: function(options) { |
|
1274 this.context.Buf.simpleRequest(REQUEST_CDMA_QUERY_PREFERRED_VOICE_PRIVACY_MODE, options); |
|
1275 }, |
|
1276 |
|
1277 /** |
|
1278 * Open Logical UICC channel (aid) for Secure Element access |
|
1279 */ |
|
1280 iccOpenChannel: function(options) { |
|
1281 if (DEBUG) { |
|
1282 this.context.debug("iccOpenChannel: " + JSON.stringify(options)); |
|
1283 } |
|
1284 |
|
1285 let Buf = this.context.Buf; |
|
1286 Buf.newParcel(REQUEST_SIM_OPEN_CHANNEL, options); |
|
1287 Buf.writeString(options.aid); |
|
1288 Buf.sendParcel(); |
|
1289 }, |
|
1290 |
|
1291 /** |
|
1292 * Exchange APDU data on an open Logical UICC channel |
|
1293 */ |
|
1294 iccExchangeAPDU: function(options) { |
|
1295 if (DEBUG) this.context.debug("iccExchangeAPDU: " + JSON.stringify(options)); |
|
1296 |
|
1297 let cla = options.apdu.cla; |
|
1298 let command = options.apdu.command; |
|
1299 let channel = options.channel; |
|
1300 let path = options.apdu.path || ""; |
|
1301 let data = options.apdu.data || ""; |
|
1302 let data2 = options.apdu.data2 || ""; |
|
1303 |
|
1304 let p1 = options.apdu.p1; |
|
1305 let p2 = options.apdu.p2; |
|
1306 let p3 = options.apdu.p3; // Extra |
|
1307 |
|
1308 let Buf = this.context.Buf; |
|
1309 Buf.newParcel(REQUEST_SIM_ACCESS_CHANNEL, options); |
|
1310 Buf.writeInt32(cla); |
|
1311 Buf.writeInt32(command); |
|
1312 Buf.writeInt32(channel); |
|
1313 Buf.writeString(path); // path |
|
1314 Buf.writeInt32(p1); |
|
1315 Buf.writeInt32(p2); |
|
1316 Buf.writeInt32(p3); |
|
1317 Buf.writeString(data); // generic data field. |
|
1318 Buf.writeString(data2); |
|
1319 |
|
1320 Buf.sendParcel(); |
|
1321 }, |
|
1322 |
|
1323 /** |
|
1324 * Close Logical UICC channel |
|
1325 */ |
|
1326 iccCloseChannel: function(options) { |
|
1327 if (DEBUG) this.context.debug("iccCloseChannel: " + JSON.stringify(options)); |
|
1328 |
|
1329 let Buf = this.context.Buf; |
|
1330 Buf.newParcel(REQUEST_SIM_CLOSE_CHANNEL, options); |
|
1331 Buf.writeInt32(1); |
|
1332 Buf.writeInt32(options.channel); |
|
1333 Buf.sendParcel(); |
|
1334 }, |
|
1335 |
|
1336 /** |
|
1337 * Tell the radio to choose a specific voice/data network |
|
1338 */ |
|
1339 selectNetwork: function(options) { |
|
1340 if (DEBUG) { |
|
1341 this.context.debug("Setting manual network selection: " + |
|
1342 options.mcc + ", " + options.mnc); |
|
1343 } |
|
1344 |
|
1345 let numeric = (options.mcc && options.mnc) ? options.mcc + options.mnc : null; |
|
1346 let Buf = this.context.Buf; |
|
1347 Buf.newParcel(REQUEST_SET_NETWORK_SELECTION_MANUAL, options); |
|
1348 Buf.writeString(numeric); |
|
1349 Buf.sendParcel(); |
|
1350 }, |
|
1351 |
|
1352 /** |
|
1353 * Get current calls. |
|
1354 */ |
|
1355 getCurrentCalls: function() { |
|
1356 this.context.Buf.simpleRequest(REQUEST_GET_CURRENT_CALLS); |
|
1357 }, |
|
1358 |
|
1359 /** |
|
1360 * Get the signal strength. |
|
1361 */ |
|
1362 getSignalStrength: function() { |
|
1363 this.context.Buf.simpleRequest(REQUEST_SIGNAL_STRENGTH); |
|
1364 }, |
|
1365 |
|
1366 getIMEI: function(options) { |
|
1367 this.context.Buf.simpleRequest(REQUEST_GET_IMEI, options); |
|
1368 }, |
|
1369 |
|
1370 getIMEISV: function() { |
|
1371 this.context.Buf.simpleRequest(REQUEST_GET_IMEISV); |
|
1372 }, |
|
1373 |
|
1374 getDeviceIdentity: function() { |
|
1375 this.context.Buf.simpleRequest(REQUEST_DEVICE_IDENTITY); |
|
1376 }, |
|
1377 |
|
1378 getBasebandVersion: function() { |
|
1379 this.context.Buf.simpleRequest(REQUEST_BASEBAND_VERSION); |
|
1380 }, |
|
1381 |
|
1382 sendExitEmergencyCbModeRequest: function(options) { |
|
1383 this.context.Buf.simpleRequest(REQUEST_EXIT_EMERGENCY_CALLBACK_MODE, options); |
|
1384 }, |
|
1385 |
|
1386 getCdmaSubscription: function() { |
|
1387 this.context.Buf.simpleRequest(REQUEST_CDMA_SUBSCRIPTION); |
|
1388 }, |
|
1389 |
|
1390 exitEmergencyCbMode: function(options) { |
|
1391 // The function could be called by an API from RadioInterfaceLayer or by |
|
1392 // ril_worker itself. From ril_worker, we won't pass the parameter |
|
1393 // 'options'. In this case, it is marked as internal. |
|
1394 if (!options) { |
|
1395 options = {internal: true}; |
|
1396 } |
|
1397 this._cancelEmergencyCbModeTimeout(); |
|
1398 this.sendExitEmergencyCbModeRequest(options); |
|
1399 }, |
|
1400 |
|
1401 /** |
|
1402 * Cache the request for making an emergency call when radio is off. The |
|
1403 * request shall include two types of callback functions. 'callback' is |
|
1404 * called when radio is ready, and 'onerror' is called when turning radio |
|
1405 * on fails. |
|
1406 */ |
|
1407 cachedDialRequest : null, |
|
1408 |
|
1409 /** |
|
1410 * Dial the phone. |
|
1411 * |
|
1412 * @param number |
|
1413 * String containing the number to dial. |
|
1414 * @param clirMode |
|
1415 * Integer for showing/hidding the caller Id to the called party. |
|
1416 * @param uusInfo |
|
1417 * Integer doing something XXX TODO |
|
1418 */ |
|
1419 dial: function(options) { |
|
1420 let onerror = (function onerror(options, errorMsg) { |
|
1421 options.success = false; |
|
1422 options.errorMsg = errorMsg; |
|
1423 this.sendChromeMessage(options); |
|
1424 }).bind(this, options); |
|
1425 |
|
1426 if (this._isEmergencyNumber(options.number)) { |
|
1427 this.dialEmergencyNumber(options, onerror); |
|
1428 } else { |
|
1429 if (!this._isCdma) { |
|
1430 // TODO: Both dial() and sendMMI() functions should be unified at some |
|
1431 // point in the future. In the mean time we handle temporary CLIR MMI |
|
1432 // commands through the dial() function. Please see bug 889737. |
|
1433 let mmi = this._parseMMI(options.number); |
|
1434 if (mmi && this._isTemporaryModeCLIR(mmi)) { |
|
1435 options.number = mmi.dialNumber; |
|
1436 // In temporary mode, MMI_PROCEDURE_ACTIVATION means allowing CLI |
|
1437 // presentation, i.e. CLIR_SUPPRESSION. See TS 22.030, Annex B. |
|
1438 options.clirMode = mmi.procedure == MMI_PROCEDURE_ACTIVATION ? |
|
1439 CLIR_SUPPRESSION : CLIR_INVOCATION; |
|
1440 } |
|
1441 } |
|
1442 this.dialNonEmergencyNumber(options, onerror); |
|
1443 } |
|
1444 }, |
|
1445 |
|
1446 dialNonEmergencyNumber: function(options, onerror) { |
|
1447 if (this.radioState == GECKO_RADIOSTATE_OFF) { |
|
1448 // Notify error in establishing the call without radio. |
|
1449 onerror(GECKO_ERROR_RADIO_NOT_AVAILABLE); |
|
1450 return; |
|
1451 } |
|
1452 |
|
1453 if (this.voiceRegistrationState.emergencyCallsOnly || |
|
1454 options.isDialEmergency) { |
|
1455 onerror(RIL_CALL_FAILCAUSE_TO_GECKO_CALL_ERROR[CALL_FAIL_UNOBTAINABLE_NUMBER]); |
|
1456 return; |
|
1457 } |
|
1458 |
|
1459 // Exit emergency callback mode when user dial a non-emergency call. |
|
1460 if (this._isInEmergencyCbMode) { |
|
1461 this.exitEmergencyCbMode(); |
|
1462 } |
|
1463 |
|
1464 if (this._isCdma && Object.keys(this.currentCalls).length == 1) { |
|
1465 // Make a Cdma 3way call. |
|
1466 options.featureStr = options.number; |
|
1467 this.sendCdmaFlashCommand(options); |
|
1468 } else { |
|
1469 options.request = REQUEST_DIAL; |
|
1470 this.sendDialRequest(options); |
|
1471 } |
|
1472 }, |
|
1473 |
|
1474 dialEmergencyNumber: function(options, onerror) { |
|
1475 options.request = RILQUIRKS_REQUEST_USE_DIAL_EMERGENCY_CALL ? |
|
1476 REQUEST_DIAL_EMERGENCY_CALL : REQUEST_DIAL; |
|
1477 if (this.radioState == GECKO_RADIOSTATE_OFF) { |
|
1478 if (DEBUG) { |
|
1479 this.context.debug("Automatically enable radio for an emergency call."); |
|
1480 } |
|
1481 |
|
1482 if (!this.cachedDialRequest) { |
|
1483 this.cachedDialRequest = {}; |
|
1484 } |
|
1485 this.cachedDialRequest.onerror = onerror; |
|
1486 this.cachedDialRequest.callback = this.sendDialRequest.bind(this, options); |
|
1487 this.setRadioEnabled({enabled: true}); |
|
1488 return; |
|
1489 } |
|
1490 |
|
1491 if (this._isCdma && Object.keys(this.currentCalls).length == 1) { |
|
1492 // Make a Cdma 3way call. |
|
1493 options.featureStr = options.number; |
|
1494 this.sendCdmaFlashCommand(options); |
|
1495 } else { |
|
1496 this.sendDialRequest(options); |
|
1497 } |
|
1498 }, |
|
1499 |
|
1500 sendDialRequest: function(options) { |
|
1501 // Always succeed. |
|
1502 options.success = true; |
|
1503 this.sendChromeMessage(options); |
|
1504 this._createPendingOutgoingCall(options); |
|
1505 |
|
1506 let Buf = this.context.Buf; |
|
1507 Buf.newParcel(options.request, options); |
|
1508 Buf.writeString(options.number); |
|
1509 Buf.writeInt32(options.clirMode || 0); |
|
1510 Buf.writeInt32(options.uusInfo || 0); |
|
1511 // TODO Why do we need this extra 0? It was put it in to make this |
|
1512 // match the format of the binary message. |
|
1513 Buf.writeInt32(0); |
|
1514 Buf.sendParcel(); |
|
1515 }, |
|
1516 |
|
1517 sendCdmaFlashCommand: function(options) { |
|
1518 let Buf = this.context.Buf; |
|
1519 options.isCdma = true; |
|
1520 options.request = REQUEST_CDMA_FLASH; |
|
1521 Buf.newParcel(options.request, options); |
|
1522 Buf.writeString(options.featureStr); |
|
1523 Buf.sendParcel(); |
|
1524 }, |
|
1525 |
|
1526 /** |
|
1527 * Hang up all calls |
|
1528 */ |
|
1529 hangUpAll: function() { |
|
1530 for (let callIndex in this.currentCalls) { |
|
1531 this.hangUp({callIndex: callIndex}); |
|
1532 } |
|
1533 }, |
|
1534 |
|
1535 /** |
|
1536 * Hang up the phone. |
|
1537 * |
|
1538 * @param callIndex |
|
1539 * Call index (1-based) as reported by REQUEST_GET_CURRENT_CALLS. |
|
1540 */ |
|
1541 hangUp: function(options) { |
|
1542 let call = this.currentCalls[options.callIndex]; |
|
1543 if (!call) { |
|
1544 return; |
|
1545 } |
|
1546 |
|
1547 let callIndex = call.callIndex; |
|
1548 if (callIndex === OUTGOING_PLACEHOLDER_CALL_INDEX) { |
|
1549 if (DEBUG) this.context.debug("Hang up pending outgoing call."); |
|
1550 this._removeVoiceCall(call, GECKO_CALL_ERROR_NORMAL_CALL_CLEARING); |
|
1551 return; |
|
1552 } |
|
1553 |
|
1554 call.hangUpLocal = true; |
|
1555 |
|
1556 if (call.state === CALL_STATE_HOLDING) { |
|
1557 this.sendHangUpBackgroundRequest(callIndex); |
|
1558 } else { |
|
1559 this.sendHangUpRequest(callIndex); |
|
1560 } |
|
1561 }, |
|
1562 |
|
1563 sendHangUpRequest: function(callIndex) { |
|
1564 let Buf = this.context.Buf; |
|
1565 Buf.newParcel(REQUEST_HANGUP); |
|
1566 Buf.writeInt32(1); |
|
1567 Buf.writeInt32(callIndex); |
|
1568 Buf.sendParcel(); |
|
1569 }, |
|
1570 |
|
1571 sendHangUpBackgroundRequest: function(callIndex) { |
|
1572 let Buf = this.context.Buf; |
|
1573 Buf.simpleRequest(REQUEST_HANGUP_WAITING_OR_BACKGROUND); |
|
1574 }, |
|
1575 |
|
1576 /** |
|
1577 * Mute or unmute the radio. |
|
1578 * |
|
1579 * @param mute |
|
1580 * Boolean to indicate whether to mute or unmute the radio. |
|
1581 */ |
|
1582 setMute: function(options) { |
|
1583 let Buf = this.context.Buf; |
|
1584 Buf.newParcel(REQUEST_SET_MUTE); |
|
1585 Buf.writeInt32(1); |
|
1586 Buf.writeInt32(options.muted ? 1 : 0); |
|
1587 Buf.sendParcel(); |
|
1588 }, |
|
1589 |
|
1590 /** |
|
1591 * Answer an incoming/waiting call. |
|
1592 * |
|
1593 * @param callIndex |
|
1594 * Call index of the call to answer. |
|
1595 */ |
|
1596 answerCall: function(options) { |
|
1597 // Check for races. Since we dispatched the incoming/waiting call |
|
1598 // notification the incoming/waiting call may have changed. The main |
|
1599 // thread thinks that it is answering the call with the given index, |
|
1600 // so only answer if that is still incoming/waiting. |
|
1601 let call = this.currentCalls[options.callIndex]; |
|
1602 if (!call) { |
|
1603 return; |
|
1604 } |
|
1605 |
|
1606 let Buf = this.context.Buf; |
|
1607 switch (call.state) { |
|
1608 case CALL_STATE_INCOMING: |
|
1609 Buf.simpleRequest(REQUEST_ANSWER); |
|
1610 break; |
|
1611 case CALL_STATE_WAITING: |
|
1612 // Answer the waiting (second) call, and hold the first call. |
|
1613 Buf.simpleRequest(REQUEST_SWITCH_WAITING_OR_HOLDING_AND_ACTIVE); |
|
1614 break; |
|
1615 } |
|
1616 }, |
|
1617 |
|
1618 /** |
|
1619 * Reject an incoming/waiting call. |
|
1620 * |
|
1621 * @param callIndex |
|
1622 * Call index of the call to reject. |
|
1623 */ |
|
1624 rejectCall: function(options) { |
|
1625 // Check for races. Since we dispatched the incoming/waiting call |
|
1626 // notification the incoming/waiting call may have changed. The main |
|
1627 // thread thinks that it is rejecting the call with the given index, |
|
1628 // so only reject if that is still incoming/waiting. |
|
1629 let call = this.currentCalls[options.callIndex]; |
|
1630 if (!call) { |
|
1631 return; |
|
1632 } |
|
1633 |
|
1634 call.hangUpLocal = true; |
|
1635 |
|
1636 let Buf = this.context.Buf; |
|
1637 if (this._isCdma) { |
|
1638 // AT+CHLD=0 means "release held or UDUB." |
|
1639 Buf.simpleRequest(REQUEST_HANGUP_WAITING_OR_BACKGROUND); |
|
1640 return; |
|
1641 } |
|
1642 |
|
1643 switch (call.state) { |
|
1644 case CALL_STATE_INCOMING: |
|
1645 Buf.simpleRequest(REQUEST_UDUB); |
|
1646 break; |
|
1647 case CALL_STATE_WAITING: |
|
1648 // Reject the waiting (second) call, and remain the first call. |
|
1649 Buf.simpleRequest(REQUEST_HANGUP_WAITING_OR_BACKGROUND); |
|
1650 break; |
|
1651 } |
|
1652 }, |
|
1653 |
|
1654 holdCall: function(options) { |
|
1655 let call = this.currentCalls[options.callIndex]; |
|
1656 if (!call) { |
|
1657 options.errorMsg = GECKO_ERROR_GENERIC_FAILURE; |
|
1658 options.success = false; |
|
1659 this.sendChromeMessage(options); |
|
1660 return; |
|
1661 } |
|
1662 |
|
1663 let Buf = this.context.Buf; |
|
1664 if (this._isCdma) { |
|
1665 options.featureStr = ""; |
|
1666 this.sendCdmaFlashCommand(options); |
|
1667 } else if (call.state == CALL_STATE_ACTIVE) { |
|
1668 Buf.simpleRequest(REQUEST_SWITCH_WAITING_OR_HOLDING_AND_ACTIVE, options); |
|
1669 } |
|
1670 }, |
|
1671 |
|
1672 resumeCall: function(options) { |
|
1673 let call = this.currentCalls[options.callIndex]; |
|
1674 if (!call) { |
|
1675 options.errorMsg = GECKO_ERROR_GENERIC_FAILURE; |
|
1676 options.success = false; |
|
1677 this.sendChromeMessage(options); |
|
1678 return; |
|
1679 } |
|
1680 |
|
1681 let Buf = this.context.Buf; |
|
1682 if (this._isCdma) { |
|
1683 options.featureStr = ""; |
|
1684 this.sendCdmaFlashCommand(options); |
|
1685 } else if (call.state == CALL_STATE_HOLDING) { |
|
1686 Buf.simpleRequest(REQUEST_SWITCH_WAITING_OR_HOLDING_AND_ACTIVE, options); |
|
1687 } |
|
1688 }, |
|
1689 |
|
1690 // Flag indicating whether user has requested making a conference call. |
|
1691 _hasConferenceRequest: false, |
|
1692 |
|
1693 conferenceCall: function(options) { |
|
1694 let Buf = this.context.Buf; |
|
1695 if (this._isCdma) { |
|
1696 options.featureStr = ""; |
|
1697 this.sendCdmaFlashCommand(options); |
|
1698 } else { |
|
1699 this._hasConferenceRequest = true; |
|
1700 Buf.simpleRequest(REQUEST_CONFERENCE, options); |
|
1701 } |
|
1702 }, |
|
1703 |
|
1704 separateCall: function(options) { |
|
1705 let call = this.currentCalls[options.callIndex]; |
|
1706 if (!call) { |
|
1707 options.errorName = "removeError"; |
|
1708 options.errorMsg = GECKO_ERROR_GENERIC_FAILURE; |
|
1709 options.success = false; |
|
1710 this.sendChromeMessage(options); |
|
1711 return; |
|
1712 } |
|
1713 |
|
1714 let Buf = this.context.Buf; |
|
1715 if (this._isCdma) { |
|
1716 options.featureStr = ""; |
|
1717 this.sendCdmaFlashCommand(options); |
|
1718 } else { |
|
1719 Buf.newParcel(REQUEST_SEPARATE_CONNECTION, options); |
|
1720 Buf.writeInt32(1); |
|
1721 Buf.writeInt32(options.callIndex); |
|
1722 Buf.sendParcel(); |
|
1723 } |
|
1724 }, |
|
1725 |
|
1726 holdConference: function() { |
|
1727 if (this._isCdma) { |
|
1728 return; |
|
1729 } |
|
1730 |
|
1731 this.context.Buf.simpleRequest(REQUEST_SWITCH_WAITING_OR_HOLDING_AND_ACTIVE); |
|
1732 }, |
|
1733 |
|
1734 resumeConference: function() { |
|
1735 if (this._isCdma) { |
|
1736 return; |
|
1737 } |
|
1738 |
|
1739 this.context.Buf.simpleRequest(REQUEST_SWITCH_WAITING_OR_HOLDING_AND_ACTIVE); |
|
1740 }, |
|
1741 |
|
1742 /** |
|
1743 * Send an SMS. |
|
1744 * |
|
1745 * The `options` parameter object should contain the following attributes: |
|
1746 * |
|
1747 * @param number |
|
1748 * String containing the recipient number. |
|
1749 * @param body |
|
1750 * String containing the message text. |
|
1751 * @param envelopeId |
|
1752 * Numeric value identifying the sms request. |
|
1753 */ |
|
1754 sendSMS: function(options) { |
|
1755 options.langIndex = options.langIndex || PDU_NL_IDENTIFIER_DEFAULT; |
|
1756 options.langShiftIndex = options.langShiftIndex || PDU_NL_IDENTIFIER_DEFAULT; |
|
1757 |
|
1758 if (!options.retryCount) { |
|
1759 options.retryCount = 0; |
|
1760 } |
|
1761 |
|
1762 if (!options.segmentSeq) { |
|
1763 // Fist segment to send |
|
1764 options.segmentSeq = 1; |
|
1765 options.body = options.segments[0].body; |
|
1766 options.encodedBodyLength = options.segments[0].encodedBodyLength; |
|
1767 } |
|
1768 |
|
1769 let Buf = this.context.Buf; |
|
1770 if (this._isCdma) { |
|
1771 Buf.newParcel(REQUEST_CDMA_SEND_SMS, options); |
|
1772 this.context.CdmaPDUHelper.writeMessage(options); |
|
1773 } else { |
|
1774 Buf.newParcel(REQUEST_SEND_SMS, options); |
|
1775 Buf.writeInt32(2); |
|
1776 Buf.writeString(options.SMSC); |
|
1777 this.context.GsmPDUHelper.writeMessage(options); |
|
1778 } |
|
1779 Buf.sendParcel(); |
|
1780 }, |
|
1781 |
|
1782 /** |
|
1783 * Acknowledge the receipt and handling of an SMS. |
|
1784 * |
|
1785 * @param success |
|
1786 * Boolean indicating whether the message was successfuly handled. |
|
1787 * @param cause |
|
1788 * SMS_* constant indicating the reason for unsuccessful handling. |
|
1789 */ |
|
1790 acknowledgeGsmSms: function(success, cause) { |
|
1791 let Buf = this.context.Buf; |
|
1792 Buf.newParcel(REQUEST_SMS_ACKNOWLEDGE); |
|
1793 Buf.writeInt32(2); |
|
1794 Buf.writeInt32(success ? 1 : 0); |
|
1795 Buf.writeInt32(cause); |
|
1796 Buf.sendParcel(); |
|
1797 }, |
|
1798 |
|
1799 /** |
|
1800 * Acknowledge the receipt and handling of an SMS. |
|
1801 * |
|
1802 * @param success |
|
1803 * Boolean indicating whether the message was successfuly handled. |
|
1804 */ |
|
1805 ackSMS: function(options) { |
|
1806 if (options.result == PDU_FCS_RESERVED) { |
|
1807 return; |
|
1808 } |
|
1809 if (this._isCdma) { |
|
1810 this.acknowledgeCdmaSms(options.result == PDU_FCS_OK, options.result); |
|
1811 } else { |
|
1812 this.acknowledgeGsmSms(options.result == PDU_FCS_OK, options.result); |
|
1813 } |
|
1814 }, |
|
1815 |
|
1816 /** |
|
1817 * Acknowledge the receipt and handling of a CDMA SMS. |
|
1818 * |
|
1819 * @param success |
|
1820 * Boolean indicating whether the message was successfuly handled. |
|
1821 * @param cause |
|
1822 * SMS_* constant indicating the reason for unsuccessful handling. |
|
1823 */ |
|
1824 acknowledgeCdmaSms: function(success, cause) { |
|
1825 let Buf = this.context.Buf; |
|
1826 Buf.newParcel(REQUEST_CDMA_SMS_ACKNOWLEDGE); |
|
1827 Buf.writeInt32(success ? 0 : 1); |
|
1828 Buf.writeInt32(cause); |
|
1829 Buf.sendParcel(); |
|
1830 }, |
|
1831 |
|
1832 /** |
|
1833 * Update received MWI into EF_MWIS. |
|
1834 */ |
|
1835 updateMwis: function(options) { |
|
1836 if (this.context.ICCUtilsHelper.isICCServiceAvailable("MWIS")) { |
|
1837 this.context.SimRecordHelper.updateMWIS(options.mwi); |
|
1838 } |
|
1839 }, |
|
1840 |
|
1841 setCellBroadcastDisabled: function(options) { |
|
1842 this.cellBroadcastDisabled = options.disabled; |
|
1843 |
|
1844 // If |this.mergedCellBroadcastConfig| is null, either we haven't finished |
|
1845 // reading required SIM files, or no any channel is ever configured. In |
|
1846 // the former case, we'll call |this.updateCellBroadcastConfig()| later |
|
1847 // with correct configs; in the latter case, we don't bother resetting CB |
|
1848 // to disabled again. |
|
1849 if (this.mergedCellBroadcastConfig) { |
|
1850 this.updateCellBroadcastConfig(); |
|
1851 } |
|
1852 }, |
|
1853 |
|
1854 setCellBroadcastSearchList: function(options) { |
|
1855 let getSearchListStr = function(aSearchList) { |
|
1856 if (typeof aSearchList === "string" || aSearchList instanceof String) { |
|
1857 return aSearchList; |
|
1858 } |
|
1859 |
|
1860 // TODO: Set search list for CDMA/GSM individually. Bug 990926 |
|
1861 let prop = this._isCdma ? "cdma" : "gsm"; |
|
1862 |
|
1863 return aSearchList && aSearchList[prop]; |
|
1864 }.bind(this); |
|
1865 |
|
1866 try { |
|
1867 let str = getSearchListStr(options.searchList); |
|
1868 this.cellBroadcastConfigs.MMI = this._convertCellBroadcastSearchList(str); |
|
1869 options.success = true; |
|
1870 } catch (e) { |
|
1871 if (DEBUG) { |
|
1872 this.context.debug("Invalid Cell Broadcast search list: " + e); |
|
1873 } |
|
1874 options.success = false; |
|
1875 } |
|
1876 |
|
1877 this.sendChromeMessage(options); |
|
1878 if (!options.success) { |
|
1879 return; |
|
1880 } |
|
1881 |
|
1882 this._mergeAllCellBroadcastConfigs(); |
|
1883 }, |
|
1884 |
|
1885 updateCellBroadcastConfig: function() { |
|
1886 let activate = !this.cellBroadcastDisabled && |
|
1887 (this.mergedCellBroadcastConfig != null) && |
|
1888 (this.mergedCellBroadcastConfig.length > 0); |
|
1889 if (activate) { |
|
1890 this.setSmsBroadcastConfig(this.mergedCellBroadcastConfig); |
|
1891 } else { |
|
1892 // It's unnecessary to set config first if we're deactivating. |
|
1893 this.setSmsBroadcastActivation(false); |
|
1894 } |
|
1895 }, |
|
1896 |
|
1897 setGsmSmsBroadcastConfig: function(config) { |
|
1898 let Buf = this.context.Buf; |
|
1899 Buf.newParcel(REQUEST_GSM_SET_BROADCAST_SMS_CONFIG); |
|
1900 |
|
1901 let numConfigs = config ? config.length / 2 : 0; |
|
1902 Buf.writeInt32(numConfigs); |
|
1903 for (let i = 0; i < config.length;) { |
|
1904 Buf.writeInt32(config[i++]); |
|
1905 Buf.writeInt32(config[i++]); |
|
1906 Buf.writeInt32(0x00); |
|
1907 Buf.writeInt32(0xFF); |
|
1908 Buf.writeInt32(1); |
|
1909 } |
|
1910 |
|
1911 Buf.sendParcel(); |
|
1912 }, |
|
1913 |
|
1914 /** |
|
1915 * Send CDMA SMS broadcast config. |
|
1916 * |
|
1917 * @see 3GPP2 C.R1001 Sec. 9.2 and 9.3 |
|
1918 */ |
|
1919 setCdmaSmsBroadcastConfig: function(config) { |
|
1920 let Buf = this.context.Buf; |
|
1921 // |config| is an array of half-closed range: [[from, to), [from, to), ...]. |
|
1922 // It will be further decomposed, ex: [1, 4) => 1, 2, 3. |
|
1923 Buf.newParcel(REQUEST_CDMA_SET_BROADCAST_SMS_CONFIG); |
|
1924 |
|
1925 let numConfigs = 0; |
|
1926 for (let i = 0; i < config.length; i += 2) { |
|
1927 numConfigs += (config[i+1] - config[i]); |
|
1928 } |
|
1929 |
|
1930 Buf.writeInt32(numConfigs); |
|
1931 for (let i = 0; i < config.length;) { |
|
1932 let begin = config[i++]; |
|
1933 let end = config[i++]; |
|
1934 |
|
1935 for (let j = begin; j < end; ++j) { |
|
1936 Buf.writeInt32(j); |
|
1937 Buf.writeInt32(0); // Language Indicator: Unknown or unspecified. |
|
1938 Buf.writeInt32(1); |
|
1939 } |
|
1940 } |
|
1941 |
|
1942 Buf.sendParcel(); |
|
1943 }, |
|
1944 |
|
1945 setSmsBroadcastConfig: function(config) { |
|
1946 if (this._isCdma) { |
|
1947 this.setCdmaSmsBroadcastConfig(config); |
|
1948 } else { |
|
1949 this.setGsmSmsBroadcastConfig(config); |
|
1950 } |
|
1951 }, |
|
1952 |
|
1953 setSmsBroadcastActivation: function(activate) { |
|
1954 let parcelType = this._isCdma ? REQUEST_CDMA_SMS_BROADCAST_ACTIVATION : |
|
1955 REQUEST_GSM_SMS_BROADCAST_ACTIVATION; |
|
1956 let Buf = this.context.Buf; |
|
1957 Buf.newParcel(parcelType); |
|
1958 Buf.writeInt32(1); |
|
1959 // See hardware/ril/include/telephony/ril.h, 0 - Activate, 1 - Turn off. |
|
1960 Buf.writeInt32(activate ? 0 : 1); |
|
1961 Buf.sendParcel(); |
|
1962 }, |
|
1963 |
|
1964 /** |
|
1965 * Start a DTMF Tone. |
|
1966 * |
|
1967 * @param dtmfChar |
|
1968 * DTMF signal to send, 0-9, *, + |
|
1969 */ |
|
1970 startTone: function(options) { |
|
1971 let Buf = this.context.Buf; |
|
1972 Buf.newParcel(REQUEST_DTMF_START); |
|
1973 Buf.writeString(options.dtmfChar); |
|
1974 Buf.sendParcel(); |
|
1975 }, |
|
1976 |
|
1977 stopTone: function() { |
|
1978 this.context.Buf.simpleRequest(REQUEST_DTMF_STOP); |
|
1979 }, |
|
1980 |
|
1981 /** |
|
1982 * Send a DTMF tone. |
|
1983 * |
|
1984 * @param dtmfChar |
|
1985 * DTMF signal to send, 0-9, *, + |
|
1986 */ |
|
1987 sendTone: function(options) { |
|
1988 let Buf = this.context.Buf; |
|
1989 Buf.newParcel(REQUEST_DTMF); |
|
1990 Buf.writeString(options.dtmfChar); |
|
1991 Buf.sendParcel(); |
|
1992 }, |
|
1993 |
|
1994 /** |
|
1995 * Get the Short Message Service Center address. |
|
1996 */ |
|
1997 getSmscAddress: function(options) { |
|
1998 if (!this.SMSC) { |
|
1999 this.context.Buf.simpleRequest(REQUEST_GET_SMSC_ADDRESS, options); |
|
2000 return; |
|
2001 } |
|
2002 |
|
2003 if (!options || options.rilMessageType !== "getSmscAddress") { |
|
2004 return; |
|
2005 } |
|
2006 |
|
2007 options.smscAddress = this.SMSC; |
|
2008 options.errorMsg = RIL_ERROR_TO_GECKO_ERROR[options.rilRequestError]; |
|
2009 this.sendChromeMessage(options); |
|
2010 }, |
|
2011 |
|
2012 /** |
|
2013 * Set the Short Message Service Center address. |
|
2014 * |
|
2015 * @param smscAddress |
|
2016 * Short Message Service Center address in PDU format. |
|
2017 */ |
|
2018 setSmscAddress: function(options) { |
|
2019 let Buf = this.context.Buf; |
|
2020 Buf.newParcel(REQUEST_SET_SMSC_ADDRESS, options); |
|
2021 Buf.writeString(options.smscAddress); |
|
2022 Buf.sendParcel(); |
|
2023 }, |
|
2024 |
|
2025 /** |
|
2026 * Setup a data call. |
|
2027 * |
|
2028 * @param radioTech |
|
2029 * Integer to indicate radio technology. |
|
2030 * DATACALL_RADIOTECHNOLOGY_CDMA => CDMA. |
|
2031 * DATACALL_RADIOTECHNOLOGY_GSM => GSM. |
|
2032 * @param apn |
|
2033 * String containing the name of the APN to connect to. |
|
2034 * @param user |
|
2035 * String containing the username for the APN. |
|
2036 * @param passwd |
|
2037 * String containing the password for the APN. |
|
2038 * @param chappap |
|
2039 * Integer containing CHAP/PAP auth type. |
|
2040 * DATACALL_AUTH_NONE => PAP and CHAP is never performed. |
|
2041 * DATACALL_AUTH_PAP => PAP may be performed. |
|
2042 * DATACALL_AUTH_CHAP => CHAP may be performed. |
|
2043 * DATACALL_AUTH_PAP_OR_CHAP => PAP / CHAP may be performed. |
|
2044 * @param pdptype |
|
2045 * String containing PDP type to request. ("IP", "IPV6", ...) |
|
2046 */ |
|
2047 setupDataCall: function(options) { |
|
2048 // From ./hardware/ril/include/telephony/ril.h: |
|
2049 // ((const char **)data)[0] Radio technology to use: 0-CDMA, 1-GSM/UMTS, 2... |
|
2050 // for values above 2 this is RIL_RadioTechnology + 2. |
|
2051 // |
|
2052 // From frameworks/base/telephony/java/com/android/internal/telephony/DataConnection.java: |
|
2053 // if the mRilVersion < 6, radio technology must be GSM/UMTS or CDMA. |
|
2054 // Otherwise, it must be + 2 |
|
2055 // |
|
2056 // See also bug 901232 and 867873 |
|
2057 let radioTech; |
|
2058 if (this.v5Legacy) { |
|
2059 radioTech = this._isCdma ? DATACALL_RADIOTECHNOLOGY_CDMA |
|
2060 : DATACALL_RADIOTECHNOLOGY_GSM; |
|
2061 } else { |
|
2062 radioTech = options.radioTech + 2; |
|
2063 } |
|
2064 let Buf = this.context.Buf; |
|
2065 let token = Buf.newParcel(REQUEST_SETUP_DATA_CALL, options); |
|
2066 Buf.writeInt32(7); |
|
2067 Buf.writeString(radioTech.toString()); |
|
2068 Buf.writeString(DATACALL_PROFILE_DEFAULT.toString()); |
|
2069 Buf.writeString(options.apn); |
|
2070 Buf.writeString(options.user); |
|
2071 Buf.writeString(options.passwd); |
|
2072 Buf.writeString(options.chappap.toString()); |
|
2073 Buf.writeString(options.pdptype); |
|
2074 Buf.sendParcel(); |
|
2075 return token; |
|
2076 }, |
|
2077 |
|
2078 /** |
|
2079 * Deactivate a data call. |
|
2080 * |
|
2081 * @param cid |
|
2082 * String containing CID. |
|
2083 * @param reason |
|
2084 * One of DATACALL_DEACTIVATE_* constants. |
|
2085 */ |
|
2086 deactivateDataCall: function(options) { |
|
2087 let datacall = this.currentDataCalls[options.cid]; |
|
2088 if (!datacall) { |
|
2089 return; |
|
2090 } |
|
2091 |
|
2092 let Buf = this.context.Buf; |
|
2093 Buf.newParcel(REQUEST_DEACTIVATE_DATA_CALL, options); |
|
2094 Buf.writeInt32(2); |
|
2095 Buf.writeString(options.cid); |
|
2096 Buf.writeString(options.reason || DATACALL_DEACTIVATE_NO_REASON); |
|
2097 Buf.sendParcel(); |
|
2098 |
|
2099 datacall.state = GECKO_NETWORK_STATE_DISCONNECTING; |
|
2100 this.sendChromeMessage(datacall); |
|
2101 }, |
|
2102 |
|
2103 /** |
|
2104 * Get a list of data calls. |
|
2105 */ |
|
2106 getDataCallList: function() { |
|
2107 this.context.Buf.simpleRequest(REQUEST_DATA_CALL_LIST); |
|
2108 }, |
|
2109 |
|
2110 _attachDataRegistration: false, |
|
2111 /** |
|
2112 * Manually attach/detach data registration. |
|
2113 * |
|
2114 * @param attach |
|
2115 * Boolean value indicating attach or detach. |
|
2116 */ |
|
2117 setDataRegistration: function(options) { |
|
2118 let request = options.attach ? RIL_REQUEST_GPRS_ATTACH : |
|
2119 RIL_REQUEST_GPRS_DETACH; |
|
2120 this._attachDataRegistration = options.attach; |
|
2121 this.context.Buf.simpleRequest(request); |
|
2122 }, |
|
2123 |
|
2124 /** |
|
2125 * Get failure casue code for the most recently failed PDP context. |
|
2126 */ |
|
2127 getFailCauseCode: function(callback) { |
|
2128 this.context.Buf.simpleRequest(REQUEST_LAST_CALL_FAIL_CAUSE, |
|
2129 {callback: callback}); |
|
2130 }, |
|
2131 |
|
2132 /** |
|
2133 * Helper to parse MMI/USSD string. TS.22.030 Figure 3.5.3.2. |
|
2134 */ |
|
2135 _parseMMI: function(mmiString) { |
|
2136 if (!mmiString || !mmiString.length) { |
|
2137 return null; |
|
2138 } |
|
2139 |
|
2140 let matches = this._matchMMIRegexp(mmiString); |
|
2141 if (matches) { |
|
2142 // After successfully executing the regular expresion over the MMI string, |
|
2143 // the following match groups should contain: |
|
2144 // 1 = full MMI string that might be used as a USSD request. |
|
2145 // 2 = MMI procedure. |
|
2146 // 3 = Service code. |
|
2147 // 5 = SIA. |
|
2148 // 7 = SIB. |
|
2149 // 9 = SIC. |
|
2150 // 11 = Password registration. |
|
2151 // 12 = Dialing number. |
|
2152 return { |
|
2153 fullMMI: matches[MMI_MATCH_GROUP_FULL_MMI], |
|
2154 procedure: matches[MMI_MATCH_GROUP_MMI_PROCEDURE], |
|
2155 serviceCode: matches[MMI_MATCH_GROUP_SERVICE_CODE], |
|
2156 sia: matches[MMI_MATCH_GROUP_SIA], |
|
2157 sib: matches[MMI_MATCH_GROUP_SIB], |
|
2158 sic: matches[MMI_MATCH_GROUP_SIC], |
|
2159 pwd: matches[MMI_MATCH_GROUP_PWD_CONFIRM], |
|
2160 dialNumber: matches[MMI_MATCH_GROUP_DIALING_NUMBER] |
|
2161 }; |
|
2162 } |
|
2163 |
|
2164 if (this._isPoundString(mmiString) || |
|
2165 this._isMMIShortString(mmiString)) { |
|
2166 return { |
|
2167 fullMMI: mmiString |
|
2168 }; |
|
2169 } |
|
2170 |
|
2171 return null; |
|
2172 }, |
|
2173 |
|
2174 /** |
|
2175 * Helper to parse MMI string via regular expression. TS.22.030 Figure |
|
2176 * 3.5.3.2. |
|
2177 */ |
|
2178 _matchMMIRegexp: function(mmiString) { |
|
2179 // Regexp to parse and process the MMI code. |
|
2180 if (this._mmiRegExp == null) { |
|
2181 // The first group of the regexp takes the whole MMI string. |
|
2182 // The second group takes the MMI procedure that can be: |
|
2183 // - Activation (*SC*SI#). |
|
2184 // - Deactivation (#SC*SI#). |
|
2185 // - Interrogation (*#SC*SI#). |
|
2186 // - Registration (**SC*SI#). |
|
2187 // - Erasure (##SC*SI#). |
|
2188 // where SC = Service Code (2 or 3 digits) and SI = Supplementary Info |
|
2189 // (variable length). |
|
2190 let pattern = "((\\*[*#]?|##?)"; |
|
2191 |
|
2192 // Third group of the regexp looks for the MMI Service code, which is a |
|
2193 // 2 or 3 digits that uniquely specifies the Supplementary Service |
|
2194 // associated with the MMI code. |
|
2195 pattern += "(\\d{2,3})"; |
|
2196 |
|
2197 // Groups from 4 to 9 looks for the MMI Supplementary Information SIA, |
|
2198 // SIB and SIC. SIA may comprise e.g. a PIN code or Directory Number, |
|
2199 // SIB may be used to specify the tele or bearer service and SIC to |
|
2200 // specify the value of the "No Reply Condition Timer". Where a particular |
|
2201 // service request does not require any SI, "*SI" is not entered. The use |
|
2202 // of SIA, SIB and SIC is optional and shall be entered in any of the |
|
2203 // following formats: |
|
2204 // - *SIA*SIB*SIC# |
|
2205 // - *SIA*SIB# |
|
2206 // - *SIA**SIC# |
|
2207 // - *SIA# |
|
2208 // - **SIB*SIC# |
|
2209 // - ***SISC# |
|
2210 pattern += "(\\*([^*#]*)(\\*([^*#]*)(\\*([^*#]*)"; |
|
2211 |
|
2212 // The eleventh group takes the password for the case of a password |
|
2213 // registration procedure. |
|
2214 pattern += "(\\*([^*#]*))?)?)?)?#)"; |
|
2215 |
|
2216 // The last group takes the dial string after the #. |
|
2217 pattern += "([^#]*)"; |
|
2218 |
|
2219 this._mmiRegExp = new RegExp(pattern); |
|
2220 } |
|
2221 |
|
2222 // Regex only applys for those well-defined MMI strings (refer to TS.22.030 |
|
2223 // Annex B), otherwise, null should be the expected return value. |
|
2224 return this._mmiRegExp.exec(mmiString); |
|
2225 }, |
|
2226 |
|
2227 /** |
|
2228 * Helper to parse # string. TS.22.030 Figure 3.5.3.2. |
|
2229 */ |
|
2230 _isPoundString: function(mmiString) { |
|
2231 return (mmiString.charAt(mmiString.length - 1) === MMI_END_OF_USSD); |
|
2232 }, |
|
2233 |
|
2234 /** |
|
2235 * Helper to parse short string. TS.22.030 Figure 3.5.3.2. |
|
2236 */ |
|
2237 _isMMIShortString: function(mmiString) { |
|
2238 if (mmiString.length > 2) { |
|
2239 return false; |
|
2240 } |
|
2241 |
|
2242 if (this._isEmergencyNumber(mmiString)) { |
|
2243 return false; |
|
2244 } |
|
2245 |
|
2246 // In a call case. |
|
2247 if (Object.getOwnPropertyNames(this.currentCalls).length > 0) { |
|
2248 return true; |
|
2249 } |
|
2250 |
|
2251 if ((mmiString.length != 2) || (mmiString.charAt(0) !== '1')) { |
|
2252 return true; |
|
2253 } |
|
2254 |
|
2255 return false; |
|
2256 }, |
|
2257 |
|
2258 sendMMI: function(options) { |
|
2259 if (DEBUG) { |
|
2260 this.context.debug("SendMMI " + JSON.stringify(options)); |
|
2261 } |
|
2262 let mmiString = options.mmi; |
|
2263 let mmi = this._parseMMI(mmiString); |
|
2264 |
|
2265 let _sendMMIError = (function(errorMsg, mmiServiceCode) { |
|
2266 options.success = false; |
|
2267 options.errorMsg = errorMsg; |
|
2268 if (mmiServiceCode) { |
|
2269 options.mmiServiceCode = mmiServiceCode; |
|
2270 } |
|
2271 this.sendChromeMessage(options); |
|
2272 }).bind(this); |
|
2273 |
|
2274 function _isValidPINPUKRequest(mmiServiceCode) { |
|
2275 // The only allowed MMI procedure for ICC PIN, PIN2, PUK and PUK2 handling |
|
2276 // is "Registration" (**). |
|
2277 if (!mmi.procedure || mmi.procedure != MMI_PROCEDURE_REGISTRATION ) { |
|
2278 _sendMMIError(MMI_ERROR_KS_INVALID_ACTION, mmiServiceCode); |
|
2279 return false; |
|
2280 } |
|
2281 |
|
2282 if (!mmi.sia || !mmi.sia.length || !mmi.sib || !mmi.sib.length || |
|
2283 !mmi.sic || !mmi.sic.length) { |
|
2284 _sendMMIError(MMI_ERROR_KS_ERROR, mmiServiceCode); |
|
2285 return false; |
|
2286 } |
|
2287 |
|
2288 if (mmi.sib != mmi.sic) { |
|
2289 _sendMMIError(MMI_ERROR_KS_MISMATCH_PIN, mmiServiceCode); |
|
2290 return false; |
|
2291 } |
|
2292 |
|
2293 if (mmi.sia.length < 4 || mmi.sia.length > 8 || |
|
2294 mmi.sib.length < 4 || mmi.sib.length > 8 || |
|
2295 mmi.sic.length < 4 || mmi.sic.length > 8) { |
|
2296 _sendMMIError(MMI_ERROR_KS_INVALID_PIN, mmiServiceCode); |
|
2297 return false; |
|
2298 } |
|
2299 |
|
2300 return true; |
|
2301 } |
|
2302 |
|
2303 let _isRadioAvailable = (function(mmiServiceCode) { |
|
2304 if (this.radioState !== GECKO_RADIOSTATE_READY) { |
|
2305 _sendMMIError(GECKO_ERROR_RADIO_NOT_AVAILABLE, mmiServiceCode); |
|
2306 return false; |
|
2307 } |
|
2308 return true; |
|
2309 }).bind(this); |
|
2310 |
|
2311 // If we couldn't parse the MMI code, we'll send it as an USSD request. |
|
2312 if (mmi === null) { |
|
2313 if (this._ussdSession) { |
|
2314 if (!_isRadioAvailable(MMI_KS_SC_USSD)) { |
|
2315 return; |
|
2316 } |
|
2317 options.ussd = mmiString; |
|
2318 this.sendUSSD(options); |
|
2319 return; |
|
2320 } |
|
2321 |
|
2322 _sendMMIError(MMI_ERROR_KS_ERROR); |
|
2323 return; |
|
2324 } |
|
2325 |
|
2326 if (DEBUG) { |
|
2327 this.context.debug("MMI " + JSON.stringify(mmi)); |
|
2328 } |
|
2329 |
|
2330 // We check if the MMI service code is supported and in that case we |
|
2331 // trigger the appropriate RIL request if possible. |
|
2332 let sc = mmi.serviceCode; |
|
2333 switch (sc) { |
|
2334 // Call forwarding |
|
2335 case MMI_SC_CFU: |
|
2336 case MMI_SC_CF_BUSY: |
|
2337 case MMI_SC_CF_NO_REPLY: |
|
2338 case MMI_SC_CF_NOT_REACHABLE: |
|
2339 case MMI_SC_CF_ALL: |
|
2340 case MMI_SC_CF_ALL_CONDITIONAL: |
|
2341 if (!_isRadioAvailable(MMI_KS_SC_CALL_FORWARDING)) { |
|
2342 return; |
|
2343 } |
|
2344 // Call forwarding requires at least an action, given by the MMI |
|
2345 // procedure, and a reason, given by the MMI service code, but there |
|
2346 // is no way that we get this far without a valid procedure or service |
|
2347 // code. |
|
2348 options.mmiServiceCode = MMI_KS_SC_CALL_FORWARDING; |
|
2349 options.action = MMI_PROC_TO_CF_ACTION[mmi.procedure]; |
|
2350 options.reason = MMI_SC_TO_CF_REASON[sc]; |
|
2351 options.number = mmi.sia; |
|
2352 options.serviceClass = this._siToServiceClass(mmi.sib); |
|
2353 if (options.action == CALL_FORWARD_ACTION_QUERY_STATUS) { |
|
2354 this.queryCallForwardStatus(options); |
|
2355 return; |
|
2356 } |
|
2357 |
|
2358 options.isSetCallForward = true; |
|
2359 options.timeSeconds = mmi.sic; |
|
2360 this.setCallForward(options); |
|
2361 return; |
|
2362 |
|
2363 // Change the current ICC PIN number. |
|
2364 case MMI_SC_PIN: |
|
2365 // As defined in TS.122.030 6.6.2 to change the ICC PIN we should expect |
|
2366 // an MMI code of the form **04*OLD_PIN*NEW_PIN*NEW_PIN#, where old PIN |
|
2367 // should be entered as the SIA parameter and the new PIN as SIB and |
|
2368 // SIC. |
|
2369 if (!_isRadioAvailable(MMI_KS_SC_PIN) || |
|
2370 !_isValidPINPUKRequest(MMI_KS_SC_PIN)) { |
|
2371 return; |
|
2372 } |
|
2373 |
|
2374 options.mmiServiceCode = MMI_KS_SC_PIN; |
|
2375 options.pin = mmi.sia; |
|
2376 options.newPin = mmi.sib; |
|
2377 this.changeICCPIN(options); |
|
2378 return; |
|
2379 |
|
2380 // Change the current ICC PIN2 number. |
|
2381 case MMI_SC_PIN2: |
|
2382 // As defined in TS.122.030 6.6.2 to change the ICC PIN2 we should |
|
2383 // enter and MMI code of the form **042*OLD_PIN2*NEW_PIN2*NEW_PIN2#, |
|
2384 // where the old PIN2 should be entered as the SIA parameter and the |
|
2385 // new PIN2 as SIB and SIC. |
|
2386 if (!_isRadioAvailable(MMI_KS_SC_PIN2) || |
|
2387 !_isValidPINPUKRequest(MMI_KS_SC_PIN2)) { |
|
2388 return; |
|
2389 } |
|
2390 |
|
2391 options.mmiServiceCode = MMI_KS_SC_PIN2; |
|
2392 options.pin = mmi.sia; |
|
2393 options.newPin = mmi.sib; |
|
2394 this.changeICCPIN2(options); |
|
2395 return; |
|
2396 |
|
2397 // Unblock ICC PIN. |
|
2398 case MMI_SC_PUK: |
|
2399 // As defined in TS.122.030 6.6.3 to unblock the ICC PIN we should |
|
2400 // enter an MMI code of the form **05*PUK*NEW_PIN*NEW_PIN#, where PUK |
|
2401 // should be entered as the SIA parameter and the new PIN as SIB and |
|
2402 // SIC. |
|
2403 if (!_isRadioAvailable(MMI_KS_SC_PUK) || |
|
2404 !_isValidPINPUKRequest(MMI_KS_SC_PUK)) { |
|
2405 return; |
|
2406 } |
|
2407 |
|
2408 options.mmiServiceCode = MMI_KS_SC_PUK; |
|
2409 options.puk = mmi.sia; |
|
2410 options.newPin = mmi.sib; |
|
2411 this.enterICCPUK(options); |
|
2412 return; |
|
2413 |
|
2414 // Unblock ICC PIN2. |
|
2415 case MMI_SC_PUK2: |
|
2416 // As defined in TS.122.030 6.6.3 to unblock the ICC PIN2 we should |
|
2417 // enter an MMI code of the form **052*PUK2*NEW_PIN2*NEW_PIN2#, where |
|
2418 // PUK2 should be entered as the SIA parameter and the new PIN2 as SIB |
|
2419 // and SIC. |
|
2420 if (!_isRadioAvailable(MMI_KS_SC_PUK2) || |
|
2421 !_isValidPINPUKRequest(MMI_KS_SC_PUK2)) { |
|
2422 return; |
|
2423 } |
|
2424 |
|
2425 options.mmiServiceCode = MMI_KS_SC_PUK2; |
|
2426 options.puk = mmi.sia; |
|
2427 options.newPin = mmi.sib; |
|
2428 this.enterICCPUK2(options); |
|
2429 return; |
|
2430 |
|
2431 // IMEI |
|
2432 case MMI_SC_IMEI: |
|
2433 // A device's IMEI can't change, so we only need to request it once. |
|
2434 if (this.IMEI == null) { |
|
2435 this.getIMEI(options); |
|
2436 return; |
|
2437 } |
|
2438 // If we already had the device's IMEI, we just send it to chrome. |
|
2439 options.mmiServiceCode = MMI_KS_SC_IMEI; |
|
2440 options.success = true; |
|
2441 options.statusMessage = this.IMEI; |
|
2442 this.sendChromeMessage(options); |
|
2443 return; |
|
2444 |
|
2445 // CLIP |
|
2446 case MMI_SC_CLIP: |
|
2447 options.mmiServiceCode = MMI_KS_SC_CLIP; |
|
2448 options.procedure = mmi.procedure; |
|
2449 if (options.procedure === MMI_PROCEDURE_INTERROGATION) { |
|
2450 this.queryCLIP(options); |
|
2451 } else { |
|
2452 _sendMMIError(MMI_ERROR_KS_NOT_SUPPORTED, MMI_KS_SC_CLIP); |
|
2453 } |
|
2454 return; |
|
2455 |
|
2456 // CLIR (non-temporary ones) |
|
2457 // TODO: Both dial() and sendMMI() functions should be unified at some |
|
2458 // point in the future. In the mean time we handle temporary CLIR MMI |
|
2459 // commands through the dial() function. Please see bug 889737. |
|
2460 case MMI_SC_CLIR: |
|
2461 options.mmiServiceCode = MMI_KS_SC_CLIR; |
|
2462 options.procedure = mmi.procedure; |
|
2463 switch (options.procedure) { |
|
2464 case MMI_PROCEDURE_INTERROGATION: |
|
2465 this.getCLIR(options); |
|
2466 return; |
|
2467 case MMI_PROCEDURE_ACTIVATION: |
|
2468 options.clirMode = CLIR_INVOCATION; |
|
2469 break; |
|
2470 case MMI_PROCEDURE_DEACTIVATION: |
|
2471 options.clirMode = CLIR_SUPPRESSION; |
|
2472 break; |
|
2473 default: |
|
2474 _sendMMIError(MMI_ERROR_KS_NOT_SUPPORTED, MMI_KS_SC_CLIR); |
|
2475 return; |
|
2476 } |
|
2477 options.isSetCLIR = true; |
|
2478 this.setCLIR(options); |
|
2479 return; |
|
2480 |
|
2481 // Call barring |
|
2482 case MMI_SC_BAOC: |
|
2483 case MMI_SC_BAOIC: |
|
2484 case MMI_SC_BAOICxH: |
|
2485 case MMI_SC_BAIC: |
|
2486 case MMI_SC_BAICr: |
|
2487 case MMI_SC_BA_ALL: |
|
2488 case MMI_SC_BA_MO: |
|
2489 case MMI_SC_BA_MT: |
|
2490 options.mmiServiceCode = MMI_KS_SC_CALL_BARRING; |
|
2491 options.password = mmi.sia || ""; |
|
2492 options.serviceClass = this._siToServiceClass(mmi.sib); |
|
2493 options.facility = MMI_SC_TO_CB_FACILITY[sc]; |
|
2494 options.procedure = mmi.procedure; |
|
2495 if (mmi.procedure === MMI_PROCEDURE_INTERROGATION) { |
|
2496 this.queryICCFacilityLock(options); |
|
2497 return; |
|
2498 } |
|
2499 if (mmi.procedure === MMI_PROCEDURE_ACTIVATION) { |
|
2500 options.enabled = 1; |
|
2501 } else if (mmi.procedure === MMI_PROCEDURE_DEACTIVATION) { |
|
2502 options.enabled = 0; |
|
2503 } else { |
|
2504 _sendMMIError(MMI_ERROR_KS_NOT_SUPPORTED, MMI_KS_SC_CALL_BARRING); |
|
2505 return; |
|
2506 } |
|
2507 this.setICCFacilityLock(options); |
|
2508 return; |
|
2509 |
|
2510 // Call waiting |
|
2511 case MMI_SC_CALL_WAITING: |
|
2512 if (!_isRadioAvailable(MMI_KS_SC_CALL_WAITING)) { |
|
2513 return; |
|
2514 } |
|
2515 |
|
2516 options.mmiServiceCode = MMI_KS_SC_CALL_WAITING; |
|
2517 |
|
2518 if (mmi.procedure === MMI_PROCEDURE_INTERROGATION) { |
|
2519 this._handleQueryMMICallWaiting(options); |
|
2520 return; |
|
2521 } |
|
2522 |
|
2523 if (mmi.procedure === MMI_PROCEDURE_ACTIVATION) { |
|
2524 options.enabled = true; |
|
2525 } else if (mmi.procedure === MMI_PROCEDURE_DEACTIVATION) { |
|
2526 options.enabled = false; |
|
2527 } else { |
|
2528 _sendMMIError(MMI_ERROR_KS_NOT_SUPPORTED, MMI_KS_SC_CALL_WAITING); |
|
2529 return; |
|
2530 } |
|
2531 |
|
2532 options.serviceClass = this._siToServiceClass(mmi.sia); |
|
2533 this._handleSetMMICallWaiting(options); |
|
2534 return; |
|
2535 } |
|
2536 |
|
2537 // If the MMI code is not a known code and is a recognized USSD request, |
|
2538 // it shall still be sent as a USSD request. |
|
2539 if (mmi.fullMMI) { |
|
2540 if (!_isRadioAvailable(MMI_KS_SC_USSD)) { |
|
2541 return; |
|
2542 } |
|
2543 |
|
2544 options.ussd = mmi.fullMMI; |
|
2545 options.mmiServiceCode = MMI_KS_SC_USSD; |
|
2546 this.sendUSSD(options); |
|
2547 return; |
|
2548 } |
|
2549 |
|
2550 // At this point, the MMI string is considered as not valid MMI code and |
|
2551 // not valid USSD code. |
|
2552 _sendMMIError(MMI_ERROR_KS_ERROR); |
|
2553 }, |
|
2554 |
|
2555 /** |
|
2556 * Send USSD. |
|
2557 * |
|
2558 * @param ussd |
|
2559 * String containing the USSD code. |
|
2560 * |
|
2561 */ |
|
2562 sendUSSD: function(options) { |
|
2563 let Buf = this.context.Buf; |
|
2564 Buf.newParcel(REQUEST_SEND_USSD, options); |
|
2565 Buf.writeString(options.ussd); |
|
2566 Buf.sendParcel(); |
|
2567 }, |
|
2568 |
|
2569 /** |
|
2570 * Cancel pending USSD. |
|
2571 */ |
|
2572 cancelUSSD: function(options) { |
|
2573 options.mmiServiceCode = MMI_KS_SC_USSD; |
|
2574 this.context.Buf.simpleRequest(REQUEST_CANCEL_USSD, options); |
|
2575 }, |
|
2576 |
|
2577 /** |
|
2578 * Queries current call forward rules. |
|
2579 * |
|
2580 * @param reason |
|
2581 * One of nsIDOMMozMobileCFInfo.CALL_FORWARD_REASON_* constants. |
|
2582 * @param serviceClass |
|
2583 * One of ICC_SERVICE_CLASS_* constants. |
|
2584 * @param number |
|
2585 * Phone number of forwarding address. |
|
2586 */ |
|
2587 queryCallForwardStatus: function(options) { |
|
2588 let Buf = this.context.Buf; |
|
2589 let number = options.number || ""; |
|
2590 Buf.newParcel(REQUEST_QUERY_CALL_FORWARD_STATUS, options); |
|
2591 Buf.writeInt32(CALL_FORWARD_ACTION_QUERY_STATUS); |
|
2592 Buf.writeInt32(options.reason); |
|
2593 Buf.writeInt32(options.serviceClass || ICC_SERVICE_CLASS_NONE); |
|
2594 Buf.writeInt32(this._toaFromString(number)); |
|
2595 Buf.writeString(number); |
|
2596 Buf.writeInt32(0); |
|
2597 Buf.sendParcel(); |
|
2598 }, |
|
2599 |
|
2600 /** |
|
2601 * Configures call forward rule. |
|
2602 * |
|
2603 * @param action |
|
2604 * One of nsIDOMMozMobileCFInfo.CALL_FORWARD_ACTION_* constants. |
|
2605 * @param reason |
|
2606 * One of nsIDOMMozMobileCFInfo.CALL_FORWARD_REASON_* constants. |
|
2607 * @param serviceClass |
|
2608 * One of ICC_SERVICE_CLASS_* constants. |
|
2609 * @param number |
|
2610 * Phone number of forwarding address. |
|
2611 * @param timeSeconds |
|
2612 * Time in seconds to wait beforec all is forwarded. |
|
2613 */ |
|
2614 setCallForward: function(options) { |
|
2615 let Buf = this.context.Buf; |
|
2616 Buf.newParcel(REQUEST_SET_CALL_FORWARD, options); |
|
2617 Buf.writeInt32(options.action); |
|
2618 Buf.writeInt32(options.reason); |
|
2619 Buf.writeInt32(options.serviceClass); |
|
2620 Buf.writeInt32(this._toaFromString(options.number)); |
|
2621 Buf.writeString(options.number); |
|
2622 Buf.writeInt32(options.timeSeconds); |
|
2623 Buf.sendParcel(); |
|
2624 }, |
|
2625 |
|
2626 /** |
|
2627 * Queries current call barring rules. |
|
2628 * |
|
2629 * @param program |
|
2630 * One of nsIDOMMozMobileConnection.CALL_BARRING_PROGRAM_* constants. |
|
2631 * @param serviceClass |
|
2632 * One of ICC_SERVICE_CLASS_* constants. |
|
2633 */ |
|
2634 queryCallBarringStatus: function(options) { |
|
2635 options.facility = CALL_BARRING_PROGRAM_TO_FACILITY[options.program]; |
|
2636 options.password = ""; // For query no need to provide it. |
|
2637 this.queryICCFacilityLock(options); |
|
2638 }, |
|
2639 |
|
2640 /** |
|
2641 * Configures call barring rule. |
|
2642 * |
|
2643 * @param program |
|
2644 * One of nsIDOMMozMobileConnection.CALL_BARRING_PROGRAM_* constants. |
|
2645 * @param enabled |
|
2646 * Enable or disable the call barring. |
|
2647 * @param password |
|
2648 * Barring password. |
|
2649 * @param serviceClass |
|
2650 * One of ICC_SERVICE_CLASS_* constants. |
|
2651 */ |
|
2652 setCallBarring: function(options) { |
|
2653 options.facility = CALL_BARRING_PROGRAM_TO_FACILITY[options.program]; |
|
2654 this.setICCFacilityLock(options); |
|
2655 }, |
|
2656 |
|
2657 /** |
|
2658 * Change call barring facility password. |
|
2659 * |
|
2660 * @param pin |
|
2661 * Old password. |
|
2662 * @param newPin |
|
2663 * New password. |
|
2664 */ |
|
2665 changeCallBarringPassword: function(options) { |
|
2666 let Buf = this.context.Buf; |
|
2667 Buf.newParcel(REQUEST_CHANGE_BARRING_PASSWORD, options); |
|
2668 Buf.writeInt32(3); |
|
2669 // Set facility to ICC_CB_FACILITY_BA_ALL by following TS.22.030 clause |
|
2670 // 6.5.4 and Table B.1. |
|
2671 Buf.writeString(ICC_CB_FACILITY_BA_ALL); |
|
2672 Buf.writeString(options.pin); |
|
2673 Buf.writeString(options.newPin); |
|
2674 Buf.sendParcel(); |
|
2675 }, |
|
2676 |
|
2677 /** |
|
2678 * Handle STK CALL_SET_UP request. |
|
2679 * |
|
2680 * @param hasConfirmed |
|
2681 * Does use have confirmed the call requested from ICC? |
|
2682 */ |
|
2683 stkHandleCallSetup: function(options) { |
|
2684 let Buf = this.context.Buf; |
|
2685 Buf.newParcel(REQUEST_STK_HANDLE_CALL_SETUP_REQUESTED_FROM_SIM); |
|
2686 Buf.writeInt32(1); |
|
2687 Buf.writeInt32(options.hasConfirmed ? 1 : 0); |
|
2688 Buf.sendParcel(); |
|
2689 }, |
|
2690 |
|
2691 /** |
|
2692 * Send STK Profile Download. |
|
2693 * |
|
2694 * @param profile Profile supported by ME. |
|
2695 */ |
|
2696 sendStkTerminalProfile: function(profile) { |
|
2697 let Buf = this.context.Buf; |
|
2698 let GsmPDUHelper = this.context.GsmPDUHelper; |
|
2699 |
|
2700 Buf.newParcel(REQUEST_STK_SET_PROFILE); |
|
2701 Buf.writeInt32(profile.length * 2); |
|
2702 for (let i = 0; i < profile.length; i++) { |
|
2703 GsmPDUHelper.writeHexOctet(profile[i]); |
|
2704 } |
|
2705 Buf.writeInt32(0); |
|
2706 Buf.sendParcel(); |
|
2707 }, |
|
2708 |
|
2709 /** |
|
2710 * Send STK terminal response. |
|
2711 * |
|
2712 * @param command |
|
2713 * @param deviceIdentities |
|
2714 * @param resultCode |
|
2715 * @param [optional] itemIdentifier |
|
2716 * @param [optional] input |
|
2717 * @param [optional] isYesNo |
|
2718 * @param [optional] hasConfirmed |
|
2719 * @param [optional] localInfo |
|
2720 * @param [optional] timer |
|
2721 */ |
|
2722 sendStkTerminalResponse: function(response) { |
|
2723 if (response.hasConfirmed !== undefined) { |
|
2724 this.stkHandleCallSetup(response); |
|
2725 return; |
|
2726 } |
|
2727 |
|
2728 let Buf = this.context.Buf; |
|
2729 let ComprehensionTlvHelper = this.context.ComprehensionTlvHelper; |
|
2730 let GsmPDUHelper = this.context.GsmPDUHelper; |
|
2731 |
|
2732 let command = response.command; |
|
2733 Buf.newParcel(REQUEST_STK_SEND_TERMINAL_RESPONSE); |
|
2734 |
|
2735 // 1st mark for Parcel size |
|
2736 Buf.startCalOutgoingSize(function(size) { |
|
2737 // Parcel size is in string length, which costs 2 uint8 per char. |
|
2738 Buf.writeInt32(size / 2); |
|
2739 }); |
|
2740 |
|
2741 // Command Details |
|
2742 GsmPDUHelper.writeHexOctet(COMPREHENSIONTLV_TAG_COMMAND_DETAILS | |
|
2743 COMPREHENSIONTLV_FLAG_CR); |
|
2744 GsmPDUHelper.writeHexOctet(3); |
|
2745 if (response.command) { |
|
2746 GsmPDUHelper.writeHexOctet(command.commandNumber); |
|
2747 GsmPDUHelper.writeHexOctet(command.typeOfCommand); |
|
2748 GsmPDUHelper.writeHexOctet(command.commandQualifier); |
|
2749 } else { |
|
2750 GsmPDUHelper.writeHexOctet(0x00); |
|
2751 GsmPDUHelper.writeHexOctet(0x00); |
|
2752 GsmPDUHelper.writeHexOctet(0x00); |
|
2753 } |
|
2754 |
|
2755 // Device Identifier |
|
2756 // According to TS102.223/TS31.111 section 6.8 Structure of |
|
2757 // TERMINAL RESPONSE, "For all SIMPLE-TLV objects with Min=N, |
|
2758 // the ME should set the CR(comprehension required) flag to |
|
2759 // comprehension not required.(CR=0)" |
|
2760 // Since DEVICE_IDENTITIES and DURATION TLVs have Min=N, |
|
2761 // the CR flag is not set. |
|
2762 GsmPDUHelper.writeHexOctet(COMPREHENSIONTLV_TAG_DEVICE_ID); |
|
2763 GsmPDUHelper.writeHexOctet(2); |
|
2764 GsmPDUHelper.writeHexOctet(STK_DEVICE_ID_ME); |
|
2765 GsmPDUHelper.writeHexOctet(STK_DEVICE_ID_SIM); |
|
2766 |
|
2767 // Result |
|
2768 GsmPDUHelper.writeHexOctet(COMPREHENSIONTLV_TAG_RESULT | |
|
2769 COMPREHENSIONTLV_FLAG_CR); |
|
2770 GsmPDUHelper.writeHexOctet(1); |
|
2771 GsmPDUHelper.writeHexOctet(response.resultCode); |
|
2772 |
|
2773 // Item Identifier |
|
2774 if (response.itemIdentifier != null) { |
|
2775 GsmPDUHelper.writeHexOctet(COMPREHENSIONTLV_TAG_ITEM_ID | |
|
2776 COMPREHENSIONTLV_FLAG_CR); |
|
2777 GsmPDUHelper.writeHexOctet(1); |
|
2778 GsmPDUHelper.writeHexOctet(response.itemIdentifier); |
|
2779 } |
|
2780 |
|
2781 // No need to process Text data if user requests help information. |
|
2782 if (response.resultCode != STK_RESULT_HELP_INFO_REQUIRED) { |
|
2783 let text; |
|
2784 if (response.isYesNo !== undefined) { |
|
2785 // GET_INKEY |
|
2786 // When the ME issues a successful TERMINAL RESPONSE for a GET INKEY |
|
2787 // ("Yes/No") command with command qualifier set to "Yes/No", it shall |
|
2788 // supply the value '01' when the answer is "positive" and the value |
|
2789 // '00' when the answer is "negative" in the Text string data object. |
|
2790 text = response.isYesNo ? 0x01 : 0x00; |
|
2791 } else { |
|
2792 text = response.input; |
|
2793 } |
|
2794 |
|
2795 if (text) { |
|
2796 GsmPDUHelper.writeHexOctet(COMPREHENSIONTLV_TAG_TEXT_STRING | |
|
2797 COMPREHENSIONTLV_FLAG_CR); |
|
2798 |
|
2799 // 2nd mark for text length |
|
2800 Buf.startCalOutgoingSize(function(size) { |
|
2801 // Text length is in number of hexOctets, which costs 4 uint8 per hexOctet. |
|
2802 GsmPDUHelper.writeHexOctet(size / 4); |
|
2803 }); |
|
2804 |
|
2805 let coding = command.options.isUCS2 ? |
|
2806 STK_TEXT_CODING_UCS2 : |
|
2807 (command.options.isPacked ? |
|
2808 STK_TEXT_CODING_GSM_7BIT_PACKED : |
|
2809 STK_TEXT_CODING_GSM_8BIT); |
|
2810 GsmPDUHelper.writeHexOctet(coding); |
|
2811 |
|
2812 // Write Text String. |
|
2813 switch (coding) { |
|
2814 case STK_TEXT_CODING_UCS2: |
|
2815 GsmPDUHelper.writeUCS2String(text); |
|
2816 break; |
|
2817 case STK_TEXT_CODING_GSM_7BIT_PACKED: |
|
2818 GsmPDUHelper.writeStringAsSeptets(text, 0, 0, 0); |
|
2819 break; |
|
2820 case STK_TEXT_CODING_GSM_8BIT: |
|
2821 for (let i = 0; i < text.length; i++) { |
|
2822 GsmPDUHelper.writeHexOctet(text.charCodeAt(i)); |
|
2823 } |
|
2824 break; |
|
2825 } |
|
2826 |
|
2827 // Calculate and write text length to 2nd mark |
|
2828 Buf.stopCalOutgoingSize(); |
|
2829 } |
|
2830 } |
|
2831 |
|
2832 // Local Information |
|
2833 if (response.localInfo) { |
|
2834 let localInfo = response.localInfo; |
|
2835 |
|
2836 // Location Infomation |
|
2837 if (localInfo.locationInfo) { |
|
2838 ComprehensionTlvHelper.writeLocationInfoTlv(localInfo.locationInfo); |
|
2839 } |
|
2840 |
|
2841 // IMEI |
|
2842 if (localInfo.imei != null) { |
|
2843 let imei = localInfo.imei; |
|
2844 if (imei.length == 15) { |
|
2845 imei = imei + "0"; |
|
2846 } |
|
2847 |
|
2848 GsmPDUHelper.writeHexOctet(COMPREHENSIONTLV_TAG_IMEI); |
|
2849 GsmPDUHelper.writeHexOctet(8); |
|
2850 for (let i = 0; i < imei.length / 2; i++) { |
|
2851 GsmPDUHelper.writeHexOctet(parseInt(imei.substr(i * 2, 2), 16)); |
|
2852 } |
|
2853 } |
|
2854 |
|
2855 // Date and Time Zone |
|
2856 if (localInfo.date != null) { |
|
2857 ComprehensionTlvHelper.writeDateTimeZoneTlv(localInfo.date); |
|
2858 } |
|
2859 |
|
2860 // Language |
|
2861 if (localInfo.language) { |
|
2862 ComprehensionTlvHelper.writeLanguageTlv(localInfo.language); |
|
2863 } |
|
2864 } |
|
2865 |
|
2866 // Timer |
|
2867 if (response.timer) { |
|
2868 let timer = response.timer; |
|
2869 |
|
2870 if (timer.timerId) { |
|
2871 GsmPDUHelper.writeHexOctet(COMPREHENSIONTLV_TAG_TIMER_IDENTIFIER); |
|
2872 GsmPDUHelper.writeHexOctet(1); |
|
2873 GsmPDUHelper.writeHexOctet(timer.timerId); |
|
2874 } |
|
2875 |
|
2876 if (timer.timerValue) { |
|
2877 ComprehensionTlvHelper.writeTimerValueTlv(timer.timerValue, false); |
|
2878 } |
|
2879 } |
|
2880 |
|
2881 // Calculate and write Parcel size to 1st mark |
|
2882 Buf.stopCalOutgoingSize(); |
|
2883 |
|
2884 Buf.writeInt32(0); |
|
2885 Buf.sendParcel(); |
|
2886 }, |
|
2887 |
|
2888 /** |
|
2889 * Send STK Envelope(Menu Selection) command. |
|
2890 * |
|
2891 * @param itemIdentifier |
|
2892 * @param helpRequested |
|
2893 */ |
|
2894 sendStkMenuSelection: function(command) { |
|
2895 command.tag = BER_MENU_SELECTION_TAG; |
|
2896 command.deviceId = { |
|
2897 sourceId :STK_DEVICE_ID_KEYPAD, |
|
2898 destinationId: STK_DEVICE_ID_SIM |
|
2899 }; |
|
2900 this.sendICCEnvelopeCommand(command); |
|
2901 }, |
|
2902 |
|
2903 /** |
|
2904 * Send STK Envelope(Timer Expiration) command. |
|
2905 * |
|
2906 * @param timer |
|
2907 */ |
|
2908 sendStkTimerExpiration: function(command) { |
|
2909 command.tag = BER_TIMER_EXPIRATION_TAG; |
|
2910 command.deviceId = { |
|
2911 sourceId: STK_DEVICE_ID_ME, |
|
2912 destinationId: STK_DEVICE_ID_SIM |
|
2913 }; |
|
2914 command.timerId = command.timer.timerId; |
|
2915 command.timerValue = command.timer.timerValue; |
|
2916 this.sendICCEnvelopeCommand(command); |
|
2917 }, |
|
2918 |
|
2919 /** |
|
2920 * Send STK Envelope(Event Download) command. |
|
2921 * @param event |
|
2922 */ |
|
2923 sendStkEventDownload: function(command) { |
|
2924 command.tag = BER_EVENT_DOWNLOAD_TAG; |
|
2925 command.eventList = command.event.eventType; |
|
2926 switch (command.eventList) { |
|
2927 case STK_EVENT_TYPE_LOCATION_STATUS: |
|
2928 command.deviceId = { |
|
2929 sourceId :STK_DEVICE_ID_ME, |
|
2930 destinationId: STK_DEVICE_ID_SIM |
|
2931 }; |
|
2932 command.locationStatus = command.event.locationStatus; |
|
2933 // Location info should only be provided when locationStatus is normal. |
|
2934 if (command.locationStatus == STK_SERVICE_STATE_NORMAL) { |
|
2935 command.locationInfo = command.event.locationInfo; |
|
2936 } |
|
2937 break; |
|
2938 case STK_EVENT_TYPE_MT_CALL: |
|
2939 command.deviceId = { |
|
2940 sourceId: STK_DEVICE_ID_NETWORK, |
|
2941 destinationId: STK_DEVICE_ID_SIM |
|
2942 }; |
|
2943 command.transactionId = 0; |
|
2944 command.address = command.event.number; |
|
2945 break; |
|
2946 case STK_EVENT_TYPE_CALL_DISCONNECTED: |
|
2947 command.cause = command.event.error; |
|
2948 // Fall through. |
|
2949 case STK_EVENT_TYPE_CALL_CONNECTED: |
|
2950 command.deviceId = { |
|
2951 sourceId: (command.event.isIssuedByRemote ? |
|
2952 STK_DEVICE_ID_NETWORK : STK_DEVICE_ID_ME), |
|
2953 destinationId: STK_DEVICE_ID_SIM |
|
2954 }; |
|
2955 command.transactionId = 0; |
|
2956 break; |
|
2957 case STK_EVENT_TYPE_USER_ACTIVITY: |
|
2958 command.deviceId = { |
|
2959 sourceId: STK_DEVICE_ID_ME, |
|
2960 destinationId: STK_DEVICE_ID_SIM |
|
2961 }; |
|
2962 break; |
|
2963 case STK_EVENT_TYPE_IDLE_SCREEN_AVAILABLE: |
|
2964 command.deviceId = { |
|
2965 sourceId: STK_DEVICE_ID_DISPLAY, |
|
2966 destinationId: STK_DEVICE_ID_SIM |
|
2967 }; |
|
2968 break; |
|
2969 case STK_EVENT_TYPE_LANGUAGE_SELECTION: |
|
2970 command.deviceId = { |
|
2971 sourceId: STK_DEVICE_ID_ME, |
|
2972 destinationId: STK_DEVICE_ID_SIM |
|
2973 }; |
|
2974 command.language = command.event.language; |
|
2975 break; |
|
2976 case STK_EVENT_TYPE_BROWSER_TERMINATION: |
|
2977 command.deviceId = { |
|
2978 sourceId: STK_DEVICE_ID_ME, |
|
2979 destinationId: STK_DEVICE_ID_SIM |
|
2980 }; |
|
2981 command.terminationCause = command.event.terminationCause; |
|
2982 break; |
|
2983 } |
|
2984 this.sendICCEnvelopeCommand(command); |
|
2985 }, |
|
2986 |
|
2987 /** |
|
2988 * Send REQUEST_STK_SEND_ENVELOPE_COMMAND to ICC. |
|
2989 * |
|
2990 * @param tag |
|
2991 * @patam deviceId |
|
2992 * @param [optioanl] itemIdentifier |
|
2993 * @param [optional] helpRequested |
|
2994 * @param [optional] eventList |
|
2995 * @param [optional] locationStatus |
|
2996 * @param [optional] locationInfo |
|
2997 * @param [optional] address |
|
2998 * @param [optional] transactionId |
|
2999 * @param [optional] cause |
|
3000 * @param [optional] timerId |
|
3001 * @param [optional] timerValue |
|
3002 * @param [optional] terminationCause |
|
3003 */ |
|
3004 sendICCEnvelopeCommand: function(options) { |
|
3005 if (DEBUG) { |
|
3006 this.context.debug("Stk Envelope " + JSON.stringify(options)); |
|
3007 } |
|
3008 |
|
3009 let Buf = this.context.Buf; |
|
3010 let ComprehensionTlvHelper = this.context.ComprehensionTlvHelper; |
|
3011 let GsmPDUHelper = this.context.GsmPDUHelper; |
|
3012 |
|
3013 Buf.newParcel(REQUEST_STK_SEND_ENVELOPE_COMMAND); |
|
3014 |
|
3015 // 1st mark for Parcel size |
|
3016 Buf.startCalOutgoingSize(function(size) { |
|
3017 // Parcel size is in string length, which costs 2 uint8 per char. |
|
3018 Buf.writeInt32(size / 2); |
|
3019 }); |
|
3020 |
|
3021 // Write a BER-TLV |
|
3022 GsmPDUHelper.writeHexOctet(options.tag); |
|
3023 // 2nd mark for BER length |
|
3024 Buf.startCalOutgoingSize(function(size) { |
|
3025 // BER length is in number of hexOctets, which costs 4 uint8 per hexOctet. |
|
3026 GsmPDUHelper.writeHexOctet(size / 4); |
|
3027 }); |
|
3028 |
|
3029 // Device Identifies |
|
3030 GsmPDUHelper.writeHexOctet(COMPREHENSIONTLV_TAG_DEVICE_ID | |
|
3031 COMPREHENSIONTLV_FLAG_CR); |
|
3032 GsmPDUHelper.writeHexOctet(2); |
|
3033 GsmPDUHelper.writeHexOctet(options.deviceId.sourceId); |
|
3034 GsmPDUHelper.writeHexOctet(options.deviceId.destinationId); |
|
3035 |
|
3036 // Item Identifier |
|
3037 if (options.itemIdentifier != null) { |
|
3038 GsmPDUHelper.writeHexOctet(COMPREHENSIONTLV_TAG_ITEM_ID | |
|
3039 COMPREHENSIONTLV_FLAG_CR); |
|
3040 GsmPDUHelper.writeHexOctet(1); |
|
3041 GsmPDUHelper.writeHexOctet(options.itemIdentifier); |
|
3042 } |
|
3043 |
|
3044 // Help Request |
|
3045 if (options.helpRequested) { |
|
3046 GsmPDUHelper.writeHexOctet(COMPREHENSIONTLV_TAG_HELP_REQUEST | |
|
3047 COMPREHENSIONTLV_FLAG_CR); |
|
3048 GsmPDUHelper.writeHexOctet(0); |
|
3049 // Help Request doesn't have value |
|
3050 } |
|
3051 |
|
3052 // Event List |
|
3053 if (options.eventList != null) { |
|
3054 GsmPDUHelper.writeHexOctet(COMPREHENSIONTLV_TAG_EVENT_LIST | |
|
3055 COMPREHENSIONTLV_FLAG_CR); |
|
3056 GsmPDUHelper.writeHexOctet(1); |
|
3057 GsmPDUHelper.writeHexOctet(options.eventList); |
|
3058 } |
|
3059 |
|
3060 // Location Status |
|
3061 if (options.locationStatus != null) { |
|
3062 let len = options.locationStatus.length; |
|
3063 GsmPDUHelper.writeHexOctet(COMPREHENSIONTLV_TAG_LOCATION_STATUS | |
|
3064 COMPREHENSIONTLV_FLAG_CR); |
|
3065 GsmPDUHelper.writeHexOctet(1); |
|
3066 GsmPDUHelper.writeHexOctet(options.locationStatus); |
|
3067 } |
|
3068 |
|
3069 // Location Info |
|
3070 if (options.locationInfo) { |
|
3071 ComprehensionTlvHelper.writeLocationInfoTlv(options.locationInfo); |
|
3072 } |
|
3073 |
|
3074 // Transaction Id |
|
3075 if (options.transactionId != null) { |
|
3076 GsmPDUHelper.writeHexOctet(COMPREHENSIONTLV_TAG_TRANSACTION_ID | |
|
3077 COMPREHENSIONTLV_FLAG_CR); |
|
3078 GsmPDUHelper.writeHexOctet(1); |
|
3079 GsmPDUHelper.writeHexOctet(options.transactionId); |
|
3080 } |
|
3081 |
|
3082 // Address |
|
3083 if (options.address) { |
|
3084 GsmPDUHelper.writeHexOctet(COMPREHENSIONTLV_TAG_ADDRESS | |
|
3085 COMPREHENSIONTLV_FLAG_CR); |
|
3086 ComprehensionTlvHelper.writeLength( |
|
3087 Math.ceil(options.address.length/2) + 1 // address BCD + TON |
|
3088 ); |
|
3089 this.context.ICCPDUHelper.writeDiallingNumber(options.address); |
|
3090 } |
|
3091 |
|
3092 // Cause of disconnection. |
|
3093 if (options.cause != null) { |
|
3094 ComprehensionTlvHelper.writeCauseTlv(options.cause); |
|
3095 } |
|
3096 |
|
3097 // Timer Identifier |
|
3098 if (options.timerId != null) { |
|
3099 GsmPDUHelper.writeHexOctet(COMPREHENSIONTLV_TAG_TIMER_IDENTIFIER | |
|
3100 COMPREHENSIONTLV_FLAG_CR); |
|
3101 GsmPDUHelper.writeHexOctet(1); |
|
3102 GsmPDUHelper.writeHexOctet(options.timerId); |
|
3103 } |
|
3104 |
|
3105 // Timer Value |
|
3106 if (options.timerValue != null) { |
|
3107 ComprehensionTlvHelper.writeTimerValueTlv(options.timerValue, true); |
|
3108 } |
|
3109 |
|
3110 // Language |
|
3111 if (options.language) { |
|
3112 ComprehensionTlvHelper.writeLanguageTlv(options.language); |
|
3113 } |
|
3114 |
|
3115 // Browser Termination |
|
3116 if (options.terminationCause != null) { |
|
3117 GsmPDUHelper.writeHexOctet(COMPREHENSIONTLV_TAG_BROWSER_TERMINATION_CAUSE | |
|
3118 COMPREHENSIONTLV_FLAG_CR); |
|
3119 GsmPDUHelper.writeHexOctet(1); |
|
3120 GsmPDUHelper.writeHexOctet(options.terminationCause); |
|
3121 } |
|
3122 |
|
3123 // Calculate and write BER length to 2nd mark |
|
3124 Buf.stopCalOutgoingSize(); |
|
3125 |
|
3126 // Calculate and write Parcel size to 1st mark |
|
3127 Buf.stopCalOutgoingSize(); |
|
3128 |
|
3129 Buf.writeInt32(0); |
|
3130 Buf.sendParcel(); |
|
3131 }, |
|
3132 |
|
3133 /** |
|
3134 * Check a given number against the list of emergency numbers provided by the RIL. |
|
3135 * |
|
3136 * @param number |
|
3137 * The number to look up. |
|
3138 */ |
|
3139 _isEmergencyNumber: function(number) { |
|
3140 // Check ril provided numbers first. |
|
3141 let numbers = RIL_EMERGENCY_NUMBERS; |
|
3142 |
|
3143 if (numbers) { |
|
3144 numbers = numbers.split(","); |
|
3145 } else { |
|
3146 // No ecclist system property, so use our own list. |
|
3147 numbers = DEFAULT_EMERGENCY_NUMBERS; |
|
3148 } |
|
3149 |
|
3150 return numbers.indexOf(number) != -1; |
|
3151 }, |
|
3152 |
|
3153 /** |
|
3154 * Checks whether to temporarily suppress caller id for the call. |
|
3155 * |
|
3156 * @param mmi |
|
3157 * MMI full object. |
|
3158 */ |
|
3159 _isTemporaryModeCLIR: function(mmi) { |
|
3160 return (mmi && |
|
3161 mmi.serviceCode == MMI_SC_CLIR && |
|
3162 mmi.dialNumber && |
|
3163 (mmi.procedure == MMI_PROCEDURE_ACTIVATION || |
|
3164 mmi.procedure == MMI_PROCEDURE_DEACTIVATION)); |
|
3165 }, |
|
3166 |
|
3167 /** |
|
3168 * Report STK Service is running. |
|
3169 */ |
|
3170 reportStkServiceIsRunning: function() { |
|
3171 this.context.Buf.simpleRequest(REQUEST_REPORT_STK_SERVICE_IS_RUNNING); |
|
3172 }, |
|
3173 |
|
3174 /** |
|
3175 * Process ICC status. |
|
3176 */ |
|
3177 _processICCStatus: function(iccStatus) { |
|
3178 // If |_waitingRadioTech| is true, we should not get app information because |
|
3179 // the |_isCdma| flag is not ready yet. Otherwise we may use wrong index to |
|
3180 // get app information, especially for the case that icc card has both cdma |
|
3181 // and gsm subscription. |
|
3182 if (this._waitingRadioTech) { |
|
3183 return; |
|
3184 } |
|
3185 |
|
3186 this.iccStatus = iccStatus; |
|
3187 let newCardState; |
|
3188 let index = this._isCdma ? iccStatus.cdmaSubscriptionAppIndex : |
|
3189 iccStatus.gsmUmtsSubscriptionAppIndex; |
|
3190 let app = iccStatus.apps[index]; |
|
3191 |
|
3192 // When |iccStatus.cardState| is not CARD_STATE_PRESENT or have incorrect |
|
3193 // app information, we can not get iccId. So treat ICC as undetected. |
|
3194 if (iccStatus.cardState !== CARD_STATE_PRESENT || !app) { |
|
3195 if (this.cardState !== GECKO_CARDSTATE_UNDETECTED) { |
|
3196 this.operator = null; |
|
3197 // We should send |cardstatechange| before |iccinfochange|, otherwise we |
|
3198 // may lost cardstatechange event when icc card becomes undetected. |
|
3199 this.cardState = GECKO_CARDSTATE_UNDETECTED; |
|
3200 this.sendChromeMessage({rilMessageType: "cardstatechange", |
|
3201 cardState: this.cardState}); |
|
3202 |
|
3203 this.iccInfo = {iccType: null}; |
|
3204 this.context.ICCUtilsHelper.handleICCInfoChange(); |
|
3205 } |
|
3206 return; |
|
3207 } |
|
3208 |
|
3209 let ICCRecordHelper = this.context.ICCRecordHelper; |
|
3210 // fetchICCRecords will need to read aid, so read aid here. |
|
3211 this.aid = app.aid; |
|
3212 this.appType = app.app_type; |
|
3213 this.iccInfo.iccType = GECKO_CARD_TYPE[this.appType]; |
|
3214 // Try to get iccId only when cardState left GECKO_CARDSTATE_UNDETECTED. |
|
3215 if (iccStatus.cardState === CARD_STATE_PRESENT && |
|
3216 (this.cardState === GECKO_CARDSTATE_UNINITIALIZED || |
|
3217 this.cardState === GECKO_CARDSTATE_UNDETECTED)) { |
|
3218 ICCRecordHelper.readICCID(); |
|
3219 } |
|
3220 |
|
3221 switch (app.app_state) { |
|
3222 case CARD_APPSTATE_ILLEGAL: |
|
3223 newCardState = GECKO_CARDSTATE_ILLEGAL; |
|
3224 break; |
|
3225 case CARD_APPSTATE_PIN: |
|
3226 newCardState = GECKO_CARDSTATE_PIN_REQUIRED; |
|
3227 break; |
|
3228 case CARD_APPSTATE_PUK: |
|
3229 newCardState = GECKO_CARDSTATE_PUK_REQUIRED; |
|
3230 break; |
|
3231 case CARD_APPSTATE_SUBSCRIPTION_PERSO: |
|
3232 newCardState = PERSONSUBSTATE[app.perso_substate]; |
|
3233 break; |
|
3234 case CARD_APPSTATE_READY: |
|
3235 newCardState = GECKO_CARDSTATE_READY; |
|
3236 break; |
|
3237 case CARD_APPSTATE_UNKNOWN: |
|
3238 case CARD_APPSTATE_DETECTED: |
|
3239 // Fall through. |
|
3240 default: |
|
3241 newCardState = GECKO_CARDSTATE_UNKNOWN; |
|
3242 } |
|
3243 |
|
3244 let pin1State = app.pin1_replaced ? iccStatus.universalPINState : |
|
3245 app.pin1; |
|
3246 if (pin1State === CARD_PINSTATE_ENABLED_PERM_BLOCKED) { |
|
3247 newCardState = GECKO_CARDSTATE_PERMANENT_BLOCKED; |
|
3248 } |
|
3249 |
|
3250 if (this.cardState == newCardState) { |
|
3251 return; |
|
3252 } |
|
3253 |
|
3254 // This was moved down from CARD_APPSTATE_READY |
|
3255 this.requestNetworkInfo(); |
|
3256 if (newCardState == GECKO_CARDSTATE_READY) { |
|
3257 // For type SIM, we need to check EF_phase first. |
|
3258 // Other types of ICC we can send Terminal_Profile immediately. |
|
3259 if (this.appType == CARD_APPTYPE_SIM) { |
|
3260 this.context.SimRecordHelper.readSimPhase(); |
|
3261 } else if (RILQUIRKS_SEND_STK_PROFILE_DOWNLOAD) { |
|
3262 this.sendStkTerminalProfile(STK_SUPPORTED_TERMINAL_PROFILE); |
|
3263 } |
|
3264 |
|
3265 ICCRecordHelper.fetchICCRecords(); |
|
3266 } |
|
3267 |
|
3268 this.cardState = newCardState; |
|
3269 this.sendChromeMessage({rilMessageType: "cardstatechange", |
|
3270 cardState: this.cardState}); |
|
3271 }, |
|
3272 |
|
3273 /** |
|
3274 * Helper for processing responses of functions such as enterICC* and changeICC*. |
|
3275 */ |
|
3276 _processEnterAndChangeICCResponses: function(length, options) { |
|
3277 options.success = (options.rilRequestError === 0); |
|
3278 if (!options.success) { |
|
3279 options.errorMsg = RIL_ERROR_TO_GECKO_ERROR[options.rilRequestError]; |
|
3280 } |
|
3281 options.retryCount = length ? this.context.Buf.readInt32List()[0] : -1; |
|
3282 if (options.rilMessageType != "sendMMI") { |
|
3283 this.sendChromeMessage(options); |
|
3284 return; |
|
3285 } |
|
3286 |
|
3287 let mmiServiceCode = options.mmiServiceCode; |
|
3288 |
|
3289 if (options.success) { |
|
3290 switch (mmiServiceCode) { |
|
3291 case MMI_KS_SC_PIN: |
|
3292 options.statusMessage = MMI_SM_KS_PIN_CHANGED; |
|
3293 break; |
|
3294 case MMI_KS_SC_PIN2: |
|
3295 options.statusMessage = MMI_SM_KS_PIN2_CHANGED; |
|
3296 break; |
|
3297 case MMI_KS_SC_PUK: |
|
3298 options.statusMessage = MMI_SM_KS_PIN_UNBLOCKED; |
|
3299 break; |
|
3300 case MMI_KS_SC_PUK2: |
|
3301 options.statusMessage = MMI_SM_KS_PIN2_UNBLOCKED; |
|
3302 break; |
|
3303 } |
|
3304 } else { |
|
3305 if (options.retryCount <= 0) { |
|
3306 if (mmiServiceCode === MMI_KS_SC_PUK) { |
|
3307 options.errorMsg = MMI_ERROR_KS_SIM_BLOCKED; |
|
3308 } else if (mmiServiceCode === MMI_KS_SC_PIN) { |
|
3309 options.errorMsg = MMI_ERROR_KS_NEEDS_PUK; |
|
3310 } |
|
3311 } else { |
|
3312 if (mmiServiceCode === MMI_KS_SC_PIN || |
|
3313 mmiServiceCode === MMI_KS_SC_PIN2) { |
|
3314 options.errorMsg = MMI_ERROR_KS_BAD_PIN; |
|
3315 } else if (mmiServiceCode === MMI_KS_SC_PUK || |
|
3316 mmiServiceCode === MMI_KS_SC_PUK2) { |
|
3317 options.errorMsg = MMI_ERROR_KS_BAD_PUK; |
|
3318 } |
|
3319 if (options.retryCount !== undefined) { |
|
3320 options.additionalInformation = options.retryCount; |
|
3321 } |
|
3322 } |
|
3323 } |
|
3324 |
|
3325 this.sendChromeMessage(options); |
|
3326 }, |
|
3327 |
|
3328 // We combine all of the NETWORK_INFO_MESSAGE_TYPES into one "networkinfochange" |
|
3329 // message to the RadioInterfaceLayer, so we can avoid sending multiple |
|
3330 // VoiceInfoChanged events for both operator / voice_data_registration |
|
3331 // |
|
3332 // State management here is a little tricky. We need to know both: |
|
3333 // 1. Whether or not a response was received for each of the |
|
3334 // NETWORK_INFO_MESSAGE_TYPES |
|
3335 // 2. The outbound message that corresponds with that response -- but this |
|
3336 // only happens when internal state changes (i.e. it isn't guaranteed) |
|
3337 // |
|
3338 // To collect this state, each message response function first calls |
|
3339 // _receivedNetworkInfo, to mark the response as received. When the |
|
3340 // final response is received, a call to _sendPendingNetworkInfo is placed |
|
3341 // on the next tick of the worker thread. |
|
3342 // |
|
3343 // Since the original call to _receivedNetworkInfo happens at the top |
|
3344 // of the response handler, this gives the final handler a chance to |
|
3345 // queue up it's "changed" message by calling _sendNetworkInfoMessage if/when |
|
3346 // the internal state has actually changed. |
|
3347 _sendNetworkInfoMessage: function(type, message) { |
|
3348 if (!this._processingNetworkInfo) { |
|
3349 // We only combine these messages in the case of the combined request |
|
3350 // in requestNetworkInfo() |
|
3351 this.sendChromeMessage(message); |
|
3352 return; |
|
3353 } |
|
3354 |
|
3355 if (DEBUG) { |
|
3356 this.context.debug("Queuing " + type + " network info message: " + |
|
3357 JSON.stringify(message)); |
|
3358 } |
|
3359 this._pendingNetworkInfo[type] = message; |
|
3360 }, |
|
3361 |
|
3362 _receivedNetworkInfo: function(type) { |
|
3363 if (DEBUG) this.context.debug("Received " + type + " network info."); |
|
3364 if (!this._processingNetworkInfo) { |
|
3365 return; |
|
3366 } |
|
3367 |
|
3368 let pending = this._pendingNetworkInfo; |
|
3369 |
|
3370 // We still need to track states for events that aren't fired. |
|
3371 if (!(type in pending)) { |
|
3372 pending[type] = this.pendingNetworkType; |
|
3373 } |
|
3374 |
|
3375 // Pending network info is ready to be sent when no more messages |
|
3376 // are waiting for responses, but the combined payload hasn't been sent. |
|
3377 for (let i = 0; i < NETWORK_INFO_MESSAGE_TYPES.length; i++) { |
|
3378 let msgType = NETWORK_INFO_MESSAGE_TYPES[i]; |
|
3379 if (!(msgType in pending)) { |
|
3380 if (DEBUG) { |
|
3381 this.context.debug("Still missing some more network info, not " + |
|
3382 "notifying main thread."); |
|
3383 } |
|
3384 return; |
|
3385 } |
|
3386 } |
|
3387 |
|
3388 // Do a pass to clean up the processed messages that didn't create |
|
3389 // a response message, so we don't have unused keys in the outbound |
|
3390 // networkinfochanged message. |
|
3391 for (let key in pending) { |
|
3392 if (pending[key] == this.pendingNetworkType) { |
|
3393 delete pending[key]; |
|
3394 } |
|
3395 } |
|
3396 |
|
3397 if (DEBUG) { |
|
3398 this.context.debug("All pending network info has been received: " + |
|
3399 JSON.stringify(pending)); |
|
3400 } |
|
3401 |
|
3402 // Send the message on the next tick of the worker's loop, so we give the |
|
3403 // last message a chance to call _sendNetworkInfoMessage first. |
|
3404 setTimeout(this._sendPendingNetworkInfo.bind(this), 0); |
|
3405 }, |
|
3406 |
|
3407 _sendPendingNetworkInfo: function() { |
|
3408 this.sendChromeMessage(this._pendingNetworkInfo); |
|
3409 |
|
3410 this._processingNetworkInfo = false; |
|
3411 for (let i = 0; i < NETWORK_INFO_MESSAGE_TYPES.length; i++) { |
|
3412 delete this._pendingNetworkInfo[NETWORK_INFO_MESSAGE_TYPES[i]]; |
|
3413 } |
|
3414 |
|
3415 if (this._needRepollNetworkInfo) { |
|
3416 this._needRepollNetworkInfo = false; |
|
3417 this.requestNetworkInfo(); |
|
3418 } |
|
3419 }, |
|
3420 |
|
3421 /** |
|
3422 * Normalize the signal strength in dBm to the signal level from 0 to 100. |
|
3423 * |
|
3424 * @param signal |
|
3425 * The signal strength in dBm to normalize. |
|
3426 * @param min |
|
3427 * The signal strength in dBm maps to level 0. |
|
3428 * @param max |
|
3429 * The signal strength in dBm maps to level 100. |
|
3430 * |
|
3431 * @return level |
|
3432 * The signal level from 0 to 100. |
|
3433 */ |
|
3434 _processSignalLevel: function(signal, min, max) { |
|
3435 if (signal <= min) { |
|
3436 return 0; |
|
3437 } |
|
3438 |
|
3439 if (signal >= max) { |
|
3440 return 100; |
|
3441 } |
|
3442 |
|
3443 return Math.floor((signal - min) * 100 / (max - min)); |
|
3444 }, |
|
3445 |
|
3446 /** |
|
3447 * Process LTE signal strength to the signal info object. |
|
3448 * |
|
3449 * @param signal |
|
3450 * The signal object reported from RIL/modem. |
|
3451 * |
|
3452 * @return The object of signal strength info. |
|
3453 * Or null if invalid signal input. |
|
3454 */ |
|
3455 _processLteSignal: function(signal) { |
|
3456 // Valid values are 0-63 as defined in TS 27.007 clause 8.69. |
|
3457 if (signal.lteSignalStrength === undefined || |
|
3458 signal.lteSignalStrength < 0 || |
|
3459 signal.lteSignalStrength > 63) { |
|
3460 return null; |
|
3461 } |
|
3462 |
|
3463 let info = { |
|
3464 voice: { |
|
3465 signalStrength: null, |
|
3466 relSignalStrength: null |
|
3467 }, |
|
3468 data: { |
|
3469 signalStrength: null, |
|
3470 relSignalStrength: null |
|
3471 } |
|
3472 }; |
|
3473 |
|
3474 // TODO: Bug 982013: reconsider signalStrength/relSignalStrength APIs for |
|
3475 // GSM/CDMA/LTE, and take rsrp/rssnr into account for LTE case then. |
|
3476 let signalStrength = -111 + signal.lteSignalStrength; |
|
3477 info.voice.signalStrength = info.data.signalStrength = signalStrength; |
|
3478 // 0 and 12 are referred to AOSP's implementation. These values are not |
|
3479 // constants and can be customized based on different requirements. |
|
3480 let signalLevel = this._processSignalLevel(signal.lteSignalStrength, 0, 12); |
|
3481 info.voice.relSignalStrength = info.data.relSignalStrength = signalLevel; |
|
3482 |
|
3483 return info; |
|
3484 }, |
|
3485 |
|
3486 _processSignalStrength: function(signal) { |
|
3487 let info = { |
|
3488 voice: { |
|
3489 signalStrength: null, |
|
3490 relSignalStrength: null |
|
3491 }, |
|
3492 data: { |
|
3493 signalStrength: null, |
|
3494 relSignalStrength: null |
|
3495 } |
|
3496 }; |
|
3497 |
|
3498 // During startup, |radioTech| is not yet defined, so we need to |
|
3499 // check it separately. |
|
3500 if (("radioTech" in this.voiceRegistrationState) && |
|
3501 !this._isGsmTechGroup(this.voiceRegistrationState.radioTech)) { |
|
3502 // CDMA RSSI. |
|
3503 // Valid values are positive integers. This value is the actual RSSI value |
|
3504 // multiplied by -1. Example: If the actual RSSI is -75, then this |
|
3505 // response value will be 75. |
|
3506 if (signal.cdmaDBM && signal.cdmaDBM > 0) { |
|
3507 let signalStrength = -1 * signal.cdmaDBM; |
|
3508 info.voice.signalStrength = signalStrength; |
|
3509 |
|
3510 // -105 and -70 are referred to AOSP's implementation. These values are |
|
3511 // not constants and can be customized based on different requirement. |
|
3512 let signalLevel = this._processSignalLevel(signalStrength, -105, -70); |
|
3513 info.voice.relSignalStrength = signalLevel; |
|
3514 } |
|
3515 |
|
3516 // EVDO RSSI. |
|
3517 // Valid values are positive integers. This value is the actual RSSI value |
|
3518 // multiplied by -1. Example: If the actual RSSI is -75, then this |
|
3519 // response value will be 75. |
|
3520 if (signal.evdoDBM && signal.evdoDBM > 0) { |
|
3521 let signalStrength = -1 * signal.evdoDBM; |
|
3522 info.data.signalStrength = signalStrength; |
|
3523 |
|
3524 // -105 and -70 are referred to AOSP's implementation. These values are |
|
3525 // not constants and can be customized based on different requirement. |
|
3526 let signalLevel = this._processSignalLevel(signalStrength, -105, -70); |
|
3527 info.data.relSignalStrength = signalLevel; |
|
3528 } |
|
3529 } else { |
|
3530 // Check LTE level first, and check GSM/UMTS level next if LTE one is not |
|
3531 // valid. |
|
3532 let lteInfo = this._processLteSignal(signal); |
|
3533 if (lteInfo) { |
|
3534 info = lteInfo; |
|
3535 } else { |
|
3536 // GSM signal strength. |
|
3537 // Valid values are 0-31 as defined in TS 27.007 8.5. |
|
3538 // 0 : -113 dBm or less |
|
3539 // 1 : -111 dBm |
|
3540 // 2...30: -109...-53 dBm |
|
3541 // 31 : -51 dBm |
|
3542 if (signal.gsmSignalStrength && |
|
3543 signal.gsmSignalStrength >= 0 && |
|
3544 signal.gsmSignalStrength <= 31) { |
|
3545 let signalStrength = -113 + 2 * signal.gsmSignalStrength; |
|
3546 info.voice.signalStrength = info.data.signalStrength = signalStrength; |
|
3547 |
|
3548 // -115 and -85 are referred to AOSP's implementation. These values are |
|
3549 // not constants and can be customized based on different requirement. |
|
3550 let signalLevel = this._processSignalLevel(signalStrength, -110, -85); |
|
3551 info.voice.relSignalStrength = info.data.relSignalStrength = signalLevel; |
|
3552 } |
|
3553 } |
|
3554 } |
|
3555 |
|
3556 info.rilMessageType = "signalstrengthchange"; |
|
3557 this._sendNetworkInfoMessage(NETWORK_INFO_SIGNAL, info); |
|
3558 |
|
3559 if (this.cachedDialRequest && info.voice.signalStrength) { |
|
3560 // Radio is ready for making the cached emergency call. |
|
3561 this.cachedDialRequest.callback(); |
|
3562 this.cachedDialRequest = null; |
|
3563 } |
|
3564 }, |
|
3565 |
|
3566 /** |
|
3567 * Process the network registration flags. |
|
3568 * |
|
3569 * @return true if the state changed, false otherwise. |
|
3570 */ |
|
3571 _processCREG: function(curState, newState) { |
|
3572 let changed = false; |
|
3573 |
|
3574 let regState = this.parseInt(newState[0], NETWORK_CREG_STATE_UNKNOWN); |
|
3575 if (curState.regState === undefined || curState.regState !== regState) { |
|
3576 changed = true; |
|
3577 curState.regState = regState; |
|
3578 |
|
3579 curState.state = NETWORK_CREG_TO_GECKO_MOBILE_CONNECTION_STATE[regState]; |
|
3580 curState.connected = regState == NETWORK_CREG_STATE_REGISTERED_HOME || |
|
3581 regState == NETWORK_CREG_STATE_REGISTERED_ROAMING; |
|
3582 curState.roaming = regState == NETWORK_CREG_STATE_REGISTERED_ROAMING; |
|
3583 curState.emergencyCallsOnly = !curState.connected; |
|
3584 } |
|
3585 |
|
3586 if (!curState.cell) { |
|
3587 curState.cell = {}; |
|
3588 } |
|
3589 |
|
3590 // From TS 23.003, 0000 and 0xfffe are indicated that no valid LAI exists |
|
3591 // in MS. So we still need to report the '0000' as well. |
|
3592 let lac = this.parseInt(newState[1], -1, 16); |
|
3593 if (curState.cell.gsmLocationAreaCode === undefined || |
|
3594 curState.cell.gsmLocationAreaCode !== lac) { |
|
3595 curState.cell.gsmLocationAreaCode = lac; |
|
3596 changed = true; |
|
3597 } |
|
3598 |
|
3599 let cid = this.parseInt(newState[2], -1, 16); |
|
3600 if (curState.cell.gsmCellId === undefined || |
|
3601 curState.cell.gsmCellId !== cid) { |
|
3602 curState.cell.gsmCellId = cid; |
|
3603 changed = true; |
|
3604 } |
|
3605 |
|
3606 let radioTech = (newState[3] === undefined ? |
|
3607 NETWORK_CREG_TECH_UNKNOWN : |
|
3608 this.parseInt(newState[3], NETWORK_CREG_TECH_UNKNOWN)); |
|
3609 if (curState.radioTech === undefined || curState.radioTech !== radioTech) { |
|
3610 changed = true; |
|
3611 curState.radioTech = radioTech; |
|
3612 curState.type = GECKO_RADIO_TECH[radioTech] || null; |
|
3613 } |
|
3614 return changed; |
|
3615 }, |
|
3616 |
|
3617 _processVoiceRegistrationState: function(state) { |
|
3618 let rs = this.voiceRegistrationState; |
|
3619 let stateChanged = this._processCREG(rs, state); |
|
3620 if (stateChanged && rs.connected) { |
|
3621 this.getSmscAddress(); |
|
3622 } |
|
3623 |
|
3624 let cell = rs.cell; |
|
3625 if (this._isCdma) { |
|
3626 // Some variables below are not used. Comment them instead of removing to |
|
3627 // keep the information about state[x]. |
|
3628 let cdmaBaseStationId = this.parseInt(state[4], -1); |
|
3629 let cdmaBaseStationLatitude = this.parseInt(state[5], -2147483648); |
|
3630 let cdmaBaseStationLongitude = this.parseInt(state[6], -2147483648); |
|
3631 // let cssIndicator = this.parseInt(state[7]); |
|
3632 let cdmaSystemId = this.parseInt(state[8], -1); |
|
3633 let cdmaNetworkId = this.parseInt(state[9], -1); |
|
3634 // let roamingIndicator = this.parseInt(state[10]); |
|
3635 // let systemIsInPRL = this.parseInt(state[11]); |
|
3636 // let defaultRoamingIndicator = this.parseInt(state[12]); |
|
3637 // let reasonForDenial = this.parseInt(state[13]); |
|
3638 |
|
3639 if (cell.cdmaBaseStationId !== cdmaBaseStationId || |
|
3640 cell.cdmaBaseStationLatitude !== cdmaBaseStationLatitude || |
|
3641 cell.cdmaBaseStationLongitude !== cdmaBaseStationLongitude || |
|
3642 cell.cdmaSystemId !== cdmaSystemId || |
|
3643 cell.cdmaNetworkId !== cdmaNetworkId) { |
|
3644 stateChanged = true; |
|
3645 cell.cdmaBaseStationId = cdmaBaseStationId; |
|
3646 cell.cdmaBaseStationLatitude = cdmaBaseStationLatitude; |
|
3647 cell.cdmaBaseStationLongitude = cdmaBaseStationLongitude; |
|
3648 cell.cdmaSystemId = cdmaSystemId; |
|
3649 cell.cdmaNetworkId = cdmaNetworkId; |
|
3650 } |
|
3651 } |
|
3652 |
|
3653 if (stateChanged) { |
|
3654 rs.rilMessageType = "voiceregistrationstatechange"; |
|
3655 this._sendNetworkInfoMessage(NETWORK_INFO_VOICE_REGISTRATION_STATE, rs); |
|
3656 } |
|
3657 }, |
|
3658 |
|
3659 _processDataRegistrationState: function(state) { |
|
3660 let rs = this.dataRegistrationState; |
|
3661 let stateChanged = this._processCREG(rs, state); |
|
3662 if (stateChanged) { |
|
3663 rs.rilMessageType = "dataregistrationstatechange"; |
|
3664 this._sendNetworkInfoMessage(NETWORK_INFO_DATA_REGISTRATION_STATE, rs); |
|
3665 } |
|
3666 }, |
|
3667 |
|
3668 _processOperator: function(operatorData) { |
|
3669 if (operatorData.length < 3) { |
|
3670 if (DEBUG) { |
|
3671 this.context.debug("Expected at least 3 strings for operator."); |
|
3672 } |
|
3673 } |
|
3674 |
|
3675 if (!this.operator) { |
|
3676 this.operator = { |
|
3677 rilMessageType: "operatorchange", |
|
3678 longName: null, |
|
3679 shortName: null |
|
3680 }; |
|
3681 } |
|
3682 |
|
3683 let [longName, shortName, networkTuple] = operatorData; |
|
3684 let thisTuple = (this.operator.mcc || "") + (this.operator.mnc || ""); |
|
3685 |
|
3686 if (this.operator.longName !== longName || |
|
3687 this.operator.shortName !== shortName || |
|
3688 thisTuple !== networkTuple) { |
|
3689 |
|
3690 this.operator.mcc = null; |
|
3691 this.operator.mnc = null; |
|
3692 |
|
3693 if (networkTuple) { |
|
3694 try { |
|
3695 this._processNetworkTuple(networkTuple, this.operator); |
|
3696 } catch (e) { |
|
3697 if (DEBUG) this.context.debug("Error processing operator tuple: " + e); |
|
3698 } |
|
3699 } else { |
|
3700 // According to ril.h, the operator fields will be NULL when the operator |
|
3701 // is not currently registered. We can avoid trying to parse the numeric |
|
3702 // tuple in that case. |
|
3703 if (DEBUG) { |
|
3704 this.context.debug("Operator is currently unregistered"); |
|
3705 } |
|
3706 } |
|
3707 |
|
3708 let ICCUtilsHelper = this.context.ICCUtilsHelper; |
|
3709 let networkName; |
|
3710 // We won't get network name using PNN and OPL if voice registration isn't ready |
|
3711 if (this.voiceRegistrationState.cell && |
|
3712 this.voiceRegistrationState.cell.gsmLocationAreaCode != -1) { |
|
3713 networkName = ICCUtilsHelper.getNetworkNameFromICC( |
|
3714 this.operator.mcc, |
|
3715 this.operator.mnc, |
|
3716 this.voiceRegistrationState.cell.gsmLocationAreaCode); |
|
3717 } |
|
3718 |
|
3719 if (networkName) { |
|
3720 if (DEBUG) { |
|
3721 this.context.debug("Operator names will be overriden: " + |
|
3722 "longName = " + networkName.fullName + ", " + |
|
3723 "shortName = " + networkName.shortName); |
|
3724 } |
|
3725 |
|
3726 this.operator.longName = networkName.fullName; |
|
3727 this.operator.shortName = networkName.shortName; |
|
3728 } else { |
|
3729 this.operator.longName = longName; |
|
3730 this.operator.shortName = shortName; |
|
3731 } |
|
3732 |
|
3733 if (ICCUtilsHelper.updateDisplayCondition()) { |
|
3734 ICCUtilsHelper.handleICCInfoChange(); |
|
3735 } |
|
3736 this._sendNetworkInfoMessage(NETWORK_INFO_OPERATOR, this.operator); |
|
3737 } |
|
3738 }, |
|
3739 |
|
3740 /** |
|
3741 * Helpers for processing call state and handle the active call. |
|
3742 */ |
|
3743 _processCalls: function(newCalls) { |
|
3744 let conferenceChanged = false; |
|
3745 let clearConferenceRequest = false; |
|
3746 let pendingOutgoingCall = null; |
|
3747 |
|
3748 // Go through the calls we currently have on file and see if any of them |
|
3749 // changed state. Remove them from the newCalls map as we deal with them |
|
3750 // so that only new calls remain in the map after we're done. |
|
3751 for each (let currentCall in this.currentCalls) { |
|
3752 if (currentCall.callIndex == OUTGOING_PLACEHOLDER_CALL_INDEX) { |
|
3753 pendingOutgoingCall = currentCall; |
|
3754 continue; |
|
3755 } |
|
3756 |
|
3757 let newCall; |
|
3758 if (newCalls) { |
|
3759 newCall = newCalls[currentCall.callIndex]; |
|
3760 delete newCalls[currentCall.callIndex]; |
|
3761 } |
|
3762 |
|
3763 // Call is no longer reported by the radio. Remove from our map and send |
|
3764 // disconnected state change. |
|
3765 if (!newCall) { |
|
3766 if (this.currentConference.participants[currentCall.callIndex]) { |
|
3767 conferenceChanged = true; |
|
3768 } |
|
3769 this._removeVoiceCall(currentCall, |
|
3770 currentCall.hangUpLocal ? |
|
3771 GECKO_CALL_ERROR_NORMAL_CALL_CLEARING : null); |
|
3772 continue; |
|
3773 } |
|
3774 |
|
3775 // Call is still valid. |
|
3776 if (newCall.state == currentCall.state && |
|
3777 newCall.isMpty == currentCall.isMpty) { |
|
3778 continue; |
|
3779 } |
|
3780 |
|
3781 // State has changed. |
|
3782 if (newCall.state == CALL_STATE_INCOMING && |
|
3783 currentCall.state == CALL_STATE_WAITING) { |
|
3784 // Update the call internally but we don't notify chrome since these two |
|
3785 // states are viewed as the same one there. |
|
3786 currentCall.state = newCall.state; |
|
3787 continue; |
|
3788 } |
|
3789 |
|
3790 if (!currentCall.started && newCall.state == CALL_STATE_ACTIVE) { |
|
3791 currentCall.started = new Date().getTime(); |
|
3792 } |
|
3793 |
|
3794 if (currentCall.isMpty == newCall.isMpty && |
|
3795 newCall.state != currentCall.state) { |
|
3796 currentCall.state = newCall.state; |
|
3797 if (currentCall.isConference) { |
|
3798 conferenceChanged = true; |
|
3799 } |
|
3800 this._handleChangedCallState(currentCall); |
|
3801 continue; |
|
3802 } |
|
3803 |
|
3804 // '.isMpty' becomes false when the conference call is put on hold. |
|
3805 // We need to introduce additional 'isConference' to correctly record the |
|
3806 // real conference status |
|
3807 |
|
3808 // Update a possible conference participant when .isMpty changes. |
|
3809 if (!currentCall.isMpty && newCall.isMpty) { |
|
3810 if (this._hasConferenceRequest) { |
|
3811 conferenceChanged = true; |
|
3812 clearConferenceRequest = true; |
|
3813 currentCall.state = newCall.state; |
|
3814 currentCall.isMpty = newCall.isMpty; |
|
3815 currentCall.isConference = true; |
|
3816 this.currentConference.participants[currentCall.callIndex] = currentCall; |
|
3817 this._handleChangedCallState(currentCall); |
|
3818 } else if (currentCall.isConference) { |
|
3819 // The case happens when resuming a held conference call. |
|
3820 conferenceChanged = true; |
|
3821 currentCall.state = newCall.state; |
|
3822 currentCall.isMpty = newCall.isMpty; |
|
3823 this.currentConference.participants[currentCall.callIndex] = currentCall; |
|
3824 this._handleChangedCallState(currentCall); |
|
3825 } else { |
|
3826 // Weird. This sometimes happens when we switch two calls, but it is |
|
3827 // not a conference call. |
|
3828 currentCall.state = newCall.state; |
|
3829 this._handleChangedCallState(currentCall); |
|
3830 } |
|
3831 } else if (currentCall.isMpty && !newCall.isMpty) { |
|
3832 if (!this.currentConference.participants[newCall.callIndex]) { |
|
3833 continue; |
|
3834 } |
|
3835 |
|
3836 // '.isMpty' of a conference participant is set to false by rild when |
|
3837 // the conference call is put on hold. We don't actually know if the call |
|
3838 // still attends the conference until updating all calls finishes. We |
|
3839 // cache it for further determination. |
|
3840 if (newCall.state != CALL_STATE_HOLDING) { |
|
3841 delete this.currentConference.participants[newCall.callIndex]; |
|
3842 currentCall.state = newCall.state; |
|
3843 currentCall.isMpty = newCall.isMpty; |
|
3844 currentCall.isConference = false; |
|
3845 conferenceChanged = true; |
|
3846 this._handleChangedCallState(currentCall); |
|
3847 continue; |
|
3848 } |
|
3849 |
|
3850 if (!this.currentConference.cache) { |
|
3851 this.currentConference.cache = {}; |
|
3852 } |
|
3853 this.currentConference.cache[currentCall.callIndex] = newCall; |
|
3854 currentCall.state = newCall.state; |
|
3855 currentCall.isMpty = newCall.isMpty; |
|
3856 conferenceChanged = true; |
|
3857 } |
|
3858 } |
|
3859 |
|
3860 if (pendingOutgoingCall) { |
|
3861 if (!newCalls || Object.keys(newCalls).length === 0) { |
|
3862 // We don't get a successful call for pendingOutgoingCall. |
|
3863 this._removePendingOutgoingCall(GECKO_CALL_ERROR_UNSPECIFIED); |
|
3864 } else { |
|
3865 // Only remove it from currentCalls map. Will use the new call to |
|
3866 // replace the placeholder. |
|
3867 delete this.currentCalls[OUTGOING_PLACEHOLDER_CALL_INDEX]; |
|
3868 } |
|
3869 } |
|
3870 |
|
3871 // Go through any remaining calls that are new to us. |
|
3872 for each (let newCall in newCalls) { |
|
3873 if (newCall.isVoice) { |
|
3874 if (newCall.isMpty) { |
|
3875 conferenceChanged = true; |
|
3876 } |
|
3877 if (!pendingOutgoingCall && |
|
3878 (newCall.state === CALL_STATE_DIALING || |
|
3879 newCall.state === CALL_STATE_ALERTING)) { |
|
3880 // Receive a new outgoing call which is already hung up by user. |
|
3881 if (DEBUG) this.context.debug("Pending outgoing call is hung up by user."); |
|
3882 this.sendHangUpRequest(newCall.callIndex); |
|
3883 } else { |
|
3884 this._addNewVoiceCall(newCall); |
|
3885 } |
|
3886 } |
|
3887 } |
|
3888 |
|
3889 if (clearConferenceRequest) { |
|
3890 this._hasConferenceRequest = false; |
|
3891 } |
|
3892 if (conferenceChanged) { |
|
3893 this._ensureConference(); |
|
3894 } |
|
3895 }, |
|
3896 |
|
3897 _addNewVoiceCall: function(newCall) { |
|
3898 // Format international numbers appropriately. |
|
3899 if (newCall.number && newCall.toa == TOA_INTERNATIONAL && |
|
3900 newCall.number[0] != "+") { |
|
3901 newCall.number = "+" + newCall.number; |
|
3902 } |
|
3903 |
|
3904 if (newCall.state == CALL_STATE_INCOMING) { |
|
3905 newCall.isOutgoing = false; |
|
3906 } else if (newCall.state == CALL_STATE_DIALING) { |
|
3907 newCall.isOutgoing = true; |
|
3908 } |
|
3909 |
|
3910 // Set flag for outgoing emergency call. |
|
3911 newCall.isEmergency = newCall.isOutgoing && |
|
3912 this._isEmergencyNumber(newCall.number); |
|
3913 |
|
3914 // Set flag for conference. |
|
3915 newCall.isConference = newCall.isMpty ? true : false; |
|
3916 |
|
3917 // Add to our map. |
|
3918 if (newCall.isMpty) { |
|
3919 this.currentConference.participants[newCall.callIndex] = newCall; |
|
3920 } |
|
3921 this._handleChangedCallState(newCall); |
|
3922 this.currentCalls[newCall.callIndex] = newCall; |
|
3923 }, |
|
3924 |
|
3925 _removeVoiceCall: function(removedCall, failCause) { |
|
3926 if (this.currentConference.participants[removedCall.callIndex]) { |
|
3927 removedCall.isConference = false; |
|
3928 delete this.currentConference.participants[removedCall.callIndex]; |
|
3929 delete this.currentCalls[removedCall.callIndex]; |
|
3930 // We don't query the fail cause here as it triggers another asynchrouns |
|
3931 // request that leads to a problem of updating all conferece participants |
|
3932 // in one task. |
|
3933 this._handleDisconnectedCall(removedCall); |
|
3934 } else { |
|
3935 delete this.currentCalls[removedCall.callIndex]; |
|
3936 if (failCause) { |
|
3937 removedCall.failCause = failCause; |
|
3938 this._handleDisconnectedCall(removedCall); |
|
3939 } else { |
|
3940 this.getFailCauseCode((function(call, failCause) { |
|
3941 call.failCause = failCause; |
|
3942 this._handleDisconnectedCall(call); |
|
3943 }).bind(this, removedCall)); |
|
3944 } |
|
3945 } |
|
3946 }, |
|
3947 |
|
3948 _createPendingOutgoingCall: function(options) { |
|
3949 if (DEBUG) this.context.debug("Create a pending outgoing call."); |
|
3950 this._addNewVoiceCall({ |
|
3951 number: options.number, |
|
3952 state: CALL_STATE_DIALING, |
|
3953 callIndex: OUTGOING_PLACEHOLDER_CALL_INDEX |
|
3954 }); |
|
3955 }, |
|
3956 |
|
3957 _removePendingOutgoingCall: function(failCause) { |
|
3958 let call = this.currentCalls[OUTGOING_PLACEHOLDER_CALL_INDEX]; |
|
3959 if (!call) { |
|
3960 return; |
|
3961 } |
|
3962 |
|
3963 if (DEBUG) this.context.debug("Remove pending outgoing call."); |
|
3964 this._removeVoiceCall(pendingOutgoingCall, failCause); |
|
3965 }, |
|
3966 |
|
3967 _ensureConference: function() { |
|
3968 let oldState = this.currentConference.state; |
|
3969 let remaining = Object.keys(this.currentConference.participants); |
|
3970 |
|
3971 if (remaining.length == 1) { |
|
3972 // Remove that if only does one remain in a conference call. |
|
3973 let call = this.currentCalls[remaining[0]]; |
|
3974 call.isConference = false; |
|
3975 this._handleChangedCallState(call); |
|
3976 delete this.currentConference.participants[call.callIndex]; |
|
3977 } else if (remaining.length > 1) { |
|
3978 for each (let call in this.currentConference.cache) { |
|
3979 call.isConference = true; |
|
3980 this.currentConference.participants[call.callIndex] = call; |
|
3981 this.currentCalls[call.callIndex] = call; |
|
3982 this._handleChangedCallState(call); |
|
3983 } |
|
3984 } |
|
3985 delete this.currentConference.cache; |
|
3986 |
|
3987 // Update the conference call's state. |
|
3988 let state = CALL_STATE_UNKNOWN; |
|
3989 for each (let call in this.currentConference.participants) { |
|
3990 if (state != CALL_STATE_UNKNOWN && state != call.state) { |
|
3991 // Each participant should have the same state, otherwise something |
|
3992 // wrong happens. |
|
3993 state = CALL_STATE_UNKNOWN; |
|
3994 break; |
|
3995 } |
|
3996 state = call.state; |
|
3997 } |
|
3998 if (oldState != state) { |
|
3999 this.currentConference.state = state; |
|
4000 let message = {rilMessageType: "conferenceCallStateChanged", |
|
4001 state: state}; |
|
4002 this.sendChromeMessage(message); |
|
4003 } |
|
4004 }, |
|
4005 |
|
4006 _handleChangedCallState: function(changedCall) { |
|
4007 let message = {rilMessageType: "callStateChange", |
|
4008 call: changedCall}; |
|
4009 this.sendChromeMessage(message); |
|
4010 }, |
|
4011 |
|
4012 _handleDisconnectedCall: function(disconnectedCall) { |
|
4013 let message = {rilMessageType: "callDisconnected", |
|
4014 call: disconnectedCall}; |
|
4015 this.sendChromeMessage(message); |
|
4016 }, |
|
4017 |
|
4018 _sendDataCallError: function(message, errorCode) { |
|
4019 // Should not include token for unsolicited response. |
|
4020 delete message.rilMessageToken; |
|
4021 message.rilMessageType = "datacallerror"; |
|
4022 if (errorCode == ERROR_GENERIC_FAILURE) { |
|
4023 message.errorMsg = RIL_ERROR_TO_GECKO_ERROR[errorCode]; |
|
4024 } else { |
|
4025 message.errorMsg = RIL_DATACALL_FAILCAUSE_TO_GECKO_DATACALL_ERROR[errorCode]; |
|
4026 } |
|
4027 this.sendChromeMessage(message); |
|
4028 }, |
|
4029 |
|
4030 /** |
|
4031 * @return "deactivate" if <ifname> changes or one of the currentDataCall |
|
4032 * addresses is missing in updatedDataCall, or "identical" if no |
|
4033 * changes found, or "changed" otherwise. |
|
4034 */ |
|
4035 _compareDataCallLink: function(updatedDataCall, currentDataCall) { |
|
4036 // If network interface is changed, report as "deactivate". |
|
4037 if (updatedDataCall.ifname != currentDataCall.ifname) { |
|
4038 return "deactivate"; |
|
4039 } |
|
4040 |
|
4041 // If any existing address is missing, report as "deactivate". |
|
4042 for (let i = 0; i < currentDataCall.addresses.length; i++) { |
|
4043 let address = currentDataCall.addresses[i]; |
|
4044 if (updatedDataCall.addresses.indexOf(address) < 0) { |
|
4045 return "deactivate"; |
|
4046 } |
|
4047 } |
|
4048 |
|
4049 if (currentDataCall.addresses.length != updatedDataCall.addresses.length) { |
|
4050 // Since now all |currentDataCall.addresses| are found in |
|
4051 // |updatedDataCall.addresses|, this means one or more new addresses are |
|
4052 // reported. |
|
4053 return "changed"; |
|
4054 } |
|
4055 |
|
4056 let fields = ["gateways", "dnses"]; |
|
4057 for (let i = 0; i < fields.length; i++) { |
|
4058 // Compare <datacall>.<field>. |
|
4059 let field = fields[i]; |
|
4060 let lhs = updatedDataCall[field], rhs = currentDataCall[field]; |
|
4061 if (lhs.length != rhs.length) { |
|
4062 return "changed"; |
|
4063 } |
|
4064 for (let i = 0; i < lhs.length; i++) { |
|
4065 if (lhs[i] != rhs[i]) { |
|
4066 return "changed"; |
|
4067 } |
|
4068 } |
|
4069 } |
|
4070 |
|
4071 return "identical"; |
|
4072 }, |
|
4073 |
|
4074 _processDataCallList: function(datacalls, newDataCallOptions) { |
|
4075 // Check for possible PDP errors: We check earlier because the datacall |
|
4076 // can be removed if is the same as the current one. |
|
4077 for each (let newDataCall in datacalls) { |
|
4078 if (newDataCall.status != DATACALL_FAIL_NONE) { |
|
4079 if (newDataCallOptions) { |
|
4080 newDataCall.apn = newDataCallOptions.apn; |
|
4081 } |
|
4082 this._sendDataCallError(newDataCall, newDataCall.status); |
|
4083 } |
|
4084 } |
|
4085 |
|
4086 for each (let currentDataCall in this.currentDataCalls) { |
|
4087 let updatedDataCall; |
|
4088 if (datacalls) { |
|
4089 updatedDataCall = datacalls[currentDataCall.cid]; |
|
4090 delete datacalls[currentDataCall.cid]; |
|
4091 } |
|
4092 |
|
4093 if (!updatedDataCall) { |
|
4094 // If datacalls list is coming from REQUEST_SETUP_DATA_CALL response, |
|
4095 // we do not change state for any currentDataCalls not in datacalls list. |
|
4096 if (!newDataCallOptions) { |
|
4097 delete this.currentDataCalls[currentDataCall.cid]; |
|
4098 currentDataCall.state = GECKO_NETWORK_STATE_DISCONNECTED; |
|
4099 currentDataCall.rilMessageType = "datacallstatechange"; |
|
4100 this.sendChromeMessage(currentDataCall); |
|
4101 } |
|
4102 continue; |
|
4103 } |
|
4104 |
|
4105 if (updatedDataCall && !updatedDataCall.ifname) { |
|
4106 delete this.currentDataCalls[currentDataCall.cid]; |
|
4107 currentDataCall.state = GECKO_NETWORK_STATE_UNKNOWN; |
|
4108 currentDataCall.rilMessageType = "datacallstatechange"; |
|
4109 this.sendChromeMessage(currentDataCall); |
|
4110 continue; |
|
4111 } |
|
4112 |
|
4113 this._setDataCallGeckoState(updatedDataCall); |
|
4114 if (updatedDataCall.state != currentDataCall.state) { |
|
4115 if (updatedDataCall.state == GECKO_NETWORK_STATE_DISCONNECTED) { |
|
4116 delete this.currentDataCalls[currentDataCall.cid]; |
|
4117 } |
|
4118 currentDataCall.status = updatedDataCall.status; |
|
4119 currentDataCall.active = updatedDataCall.active; |
|
4120 currentDataCall.state = updatedDataCall.state; |
|
4121 currentDataCall.rilMessageType = "datacallstatechange"; |
|
4122 this.sendChromeMessage(currentDataCall); |
|
4123 continue; |
|
4124 } |
|
4125 |
|
4126 // State not changed, now check links. |
|
4127 let result = |
|
4128 this._compareDataCallLink(updatedDataCall, currentDataCall); |
|
4129 if (result == "identical") { |
|
4130 if (DEBUG) this.context.debug("No changes in data call."); |
|
4131 continue; |
|
4132 } |
|
4133 if (result == "deactivate") { |
|
4134 if (DEBUG) this.context.debug("Data link changed, cleanup."); |
|
4135 this.deactivateDataCall(currentDataCall); |
|
4136 continue; |
|
4137 } |
|
4138 // Minor change, just update and notify. |
|
4139 if (DEBUG) { |
|
4140 this.context.debug("Data link minor change, just update and notify."); |
|
4141 } |
|
4142 currentDataCall.addresses = updatedDataCall.addresses.slice(); |
|
4143 currentDataCall.dnses = updatedDataCall.dnses.slice(); |
|
4144 currentDataCall.gateways = updatedDataCall.gateways.slice(); |
|
4145 currentDataCall.rilMessageType = "datacallstatechange"; |
|
4146 this.sendChromeMessage(currentDataCall); |
|
4147 } |
|
4148 |
|
4149 for each (let newDataCall in datacalls) { |
|
4150 if (!newDataCall.ifname) { |
|
4151 continue; |
|
4152 } |
|
4153 |
|
4154 if (!newDataCallOptions) { |
|
4155 if (DEBUG) { |
|
4156 this.context.debug("Unexpected new data call: " + |
|
4157 JSON.stringify(newDataCall)); |
|
4158 } |
|
4159 continue; |
|
4160 } |
|
4161 |
|
4162 this.currentDataCalls[newDataCall.cid] = newDataCall; |
|
4163 this._setDataCallGeckoState(newDataCall); |
|
4164 |
|
4165 newDataCall.radioTech = newDataCallOptions.radioTech; |
|
4166 newDataCall.apn = newDataCallOptions.apn; |
|
4167 newDataCall.user = newDataCallOptions.user; |
|
4168 newDataCall.passwd = newDataCallOptions.passwd; |
|
4169 newDataCall.chappap = newDataCallOptions.chappap; |
|
4170 newDataCall.pdptype = newDataCallOptions.pdptype; |
|
4171 newDataCallOptions = null; |
|
4172 |
|
4173 newDataCall.rilMessageType = "datacallstatechange"; |
|
4174 this.sendChromeMessage(newDataCall); |
|
4175 } |
|
4176 }, |
|
4177 |
|
4178 _setDataCallGeckoState: function(datacall) { |
|
4179 switch (datacall.active) { |
|
4180 case DATACALL_INACTIVE: |
|
4181 datacall.state = GECKO_NETWORK_STATE_DISCONNECTED; |
|
4182 break; |
|
4183 case DATACALL_ACTIVE_DOWN: |
|
4184 case DATACALL_ACTIVE_UP: |
|
4185 datacall.state = GECKO_NETWORK_STATE_CONNECTED; |
|
4186 break; |
|
4187 } |
|
4188 }, |
|
4189 |
|
4190 _processSuppSvcNotification: function(info) { |
|
4191 if (DEBUG) { |
|
4192 this.context.debug("handle supp svc notification: " + JSON.stringify(info)); |
|
4193 } |
|
4194 |
|
4195 if (info.notificationType !== 1) { |
|
4196 // We haven't supported MO intermediate result code, i.e. |
|
4197 // info.notificationType === 0, which refers to code1 defined in 3GPP |
|
4198 // 27.007 7.17. We only support partial MT unsolicited result code, |
|
4199 // referring to code2, for now. |
|
4200 return; |
|
4201 } |
|
4202 |
|
4203 let notification = null; |
|
4204 let callIndex = -1; |
|
4205 |
|
4206 switch (info.code) { |
|
4207 case SUPP_SVC_NOTIFICATION_CODE2_PUT_ON_HOLD: |
|
4208 case SUPP_SVC_NOTIFICATION_CODE2_RETRIEVED: |
|
4209 notification = GECKO_SUPP_SVC_NOTIFICATION_FROM_CODE2[info.code]; |
|
4210 break; |
|
4211 default: |
|
4212 // Notification type not supported. |
|
4213 return; |
|
4214 } |
|
4215 |
|
4216 // Get the target call object for this notification. |
|
4217 let currentCallIndexes = Object.keys(this.currentCalls); |
|
4218 if (currentCallIndexes.length === 1) { |
|
4219 // Only one call exists. This should be the target. |
|
4220 callIndex = currentCallIndexes[0]; |
|
4221 } else { |
|
4222 // Find the call in |currentCalls| by the given number. |
|
4223 if (info.number) { |
|
4224 for each (let currentCall in this.currentCalls) { |
|
4225 if (currentCall.number == info.number) { |
|
4226 callIndex = currentCall.callIndex; |
|
4227 break; |
|
4228 } |
|
4229 } |
|
4230 } |
|
4231 } |
|
4232 |
|
4233 let message = {rilMessageType: "suppSvcNotification", |
|
4234 notification: notification, |
|
4235 callIndex: callIndex}; |
|
4236 this.sendChromeMessage(message); |
|
4237 }, |
|
4238 |
|
4239 _cancelEmergencyCbModeTimeout: function() { |
|
4240 if (this._exitEmergencyCbModeTimeoutID) { |
|
4241 clearTimeout(this._exitEmergencyCbModeTimeoutID); |
|
4242 this._exitEmergencyCbModeTimeoutID = null; |
|
4243 } |
|
4244 }, |
|
4245 |
|
4246 _handleChangedEmergencyCbMode: function(active) { |
|
4247 this._isInEmergencyCbMode = active; |
|
4248 |
|
4249 // Clear the existed timeout event. |
|
4250 this._cancelEmergencyCbModeTimeout(); |
|
4251 |
|
4252 // Start a new timeout event when entering the mode. |
|
4253 if (active) { |
|
4254 this._exitEmergencyCbModeTimeoutID = setTimeout( |
|
4255 this.exitEmergencyCbMode.bind(this), EMERGENCY_CB_MODE_TIMEOUT_MS); |
|
4256 } |
|
4257 |
|
4258 let message = {rilMessageType: "emergencyCbModeChange", |
|
4259 active: active, |
|
4260 timeoutMs: EMERGENCY_CB_MODE_TIMEOUT_MS}; |
|
4261 this.sendChromeMessage(message); |
|
4262 }, |
|
4263 |
|
4264 _processNetworks: function() { |
|
4265 let strings = this.context.Buf.readStringList(); |
|
4266 let networks = []; |
|
4267 |
|
4268 for (let i = 0; i < strings.length; i += 4) { |
|
4269 let network = { |
|
4270 longName: strings[i], |
|
4271 shortName: strings[i + 1], |
|
4272 mcc: null, |
|
4273 mnc: null, |
|
4274 state: null |
|
4275 }; |
|
4276 |
|
4277 let networkTuple = strings[i + 2]; |
|
4278 try { |
|
4279 this._processNetworkTuple(networkTuple, network); |
|
4280 } catch (e) { |
|
4281 if (DEBUG) this.context.debug("Error processing operator tuple: " + e); |
|
4282 } |
|
4283 |
|
4284 let state = strings[i + 3]; |
|
4285 if (state === NETWORK_STATE_UNKNOWN) { |
|
4286 // TODO: looks like this might conflict in style with |
|
4287 // GECKO_NETWORK_STYLE_UNKNOWN / nsINetworkManager |
|
4288 state = GECKO_QAN_STATE_UNKNOWN; |
|
4289 } |
|
4290 |
|
4291 network.state = state; |
|
4292 networks.push(network); |
|
4293 } |
|
4294 return networks; |
|
4295 }, |
|
4296 |
|
4297 /** |
|
4298 * The "numeric" portion of the operator info is a tuple |
|
4299 * containing MCC (country code) and MNC (network code). |
|
4300 * AFAICT, MCC should always be 3 digits, making the remaining |
|
4301 * portion the MNC. |
|
4302 */ |
|
4303 _processNetworkTuple: function(networkTuple, network) { |
|
4304 let tupleLen = networkTuple.length; |
|
4305 |
|
4306 if (tupleLen == 5 || tupleLen == 6) { |
|
4307 network.mcc = networkTuple.substr(0, 3); |
|
4308 network.mnc = networkTuple.substr(3); |
|
4309 } else { |
|
4310 network.mcc = null; |
|
4311 network.mnc = null; |
|
4312 |
|
4313 throw new Error("Invalid network tuple (should be 5 or 6 digits): " + networkTuple); |
|
4314 } |
|
4315 }, |
|
4316 |
|
4317 /** |
|
4318 * Check if GSM radio access technology group. |
|
4319 */ |
|
4320 _isGsmTechGroup: function(radioTech) { |
|
4321 if (!radioTech) { |
|
4322 return true; |
|
4323 } |
|
4324 |
|
4325 switch(radioTech) { |
|
4326 case NETWORK_CREG_TECH_GPRS: |
|
4327 case NETWORK_CREG_TECH_EDGE: |
|
4328 case NETWORK_CREG_TECH_UMTS: |
|
4329 case NETWORK_CREG_TECH_HSDPA: |
|
4330 case NETWORK_CREG_TECH_HSUPA: |
|
4331 case NETWORK_CREG_TECH_HSPA: |
|
4332 case NETWORK_CREG_TECH_LTE: |
|
4333 case NETWORK_CREG_TECH_HSPAP: |
|
4334 case NETWORK_CREG_TECH_GSM: |
|
4335 return true; |
|
4336 } |
|
4337 |
|
4338 return false; |
|
4339 }, |
|
4340 |
|
4341 /** |
|
4342 * Process radio technology change. |
|
4343 */ |
|
4344 _processRadioTech: function(radioTech) { |
|
4345 let isCdma = !this._isGsmTechGroup(radioTech); |
|
4346 this.radioTech = radioTech; |
|
4347 |
|
4348 if (DEBUG) { |
|
4349 this.context.debug("Radio tech is set to: " + GECKO_RADIO_TECH[radioTech] + |
|
4350 ", it is a " + (isCdma?"cdma":"gsm") + " technology"); |
|
4351 } |
|
4352 |
|
4353 // We should request SIM information when |
|
4354 // 1. Radio state has been changed, so we are waiting for radioTech or |
|
4355 // 2. isCdma is different from this._isCdma. |
|
4356 if (this._waitingRadioTech || isCdma != this._isCdma) { |
|
4357 this._isCdma = isCdma; |
|
4358 this._waitingRadioTech = false; |
|
4359 if (this._isCdma) { |
|
4360 this.getDeviceIdentity(); |
|
4361 } else { |
|
4362 this.getIMEI(); |
|
4363 this.getIMEISV(); |
|
4364 } |
|
4365 this.getICCStatus(); |
|
4366 } |
|
4367 }, |
|
4368 |
|
4369 /** |
|
4370 * Helper for returning the TOA for the given dial string. |
|
4371 */ |
|
4372 _toaFromString: function(number) { |
|
4373 let toa = TOA_UNKNOWN; |
|
4374 if (number && number.length > 0 && number[0] == '+') { |
|
4375 toa = TOA_INTERNATIONAL; |
|
4376 } |
|
4377 return toa; |
|
4378 }, |
|
4379 |
|
4380 /** |
|
4381 * Helper for translating basic service group to call forwarding service class |
|
4382 * parameter. |
|
4383 */ |
|
4384 _siToServiceClass: function(si) { |
|
4385 if (!si) { |
|
4386 return ICC_SERVICE_CLASS_NONE; |
|
4387 } |
|
4388 |
|
4389 let serviceCode = parseInt(si, 10); |
|
4390 switch (serviceCode) { |
|
4391 case 10: |
|
4392 return ICC_SERVICE_CLASS_SMS + ICC_SERVICE_CLASS_FAX + ICC_SERVICE_CLASS_VOICE; |
|
4393 case 11: |
|
4394 return ICC_SERVICE_CLASS_VOICE; |
|
4395 case 12: |
|
4396 return ICC_SERVICE_CLASS_SMS + ICC_SERVICE_CLASS_FAX; |
|
4397 case 13: |
|
4398 return ICC_SERVICE_CLASS_FAX; |
|
4399 case 16: |
|
4400 return ICC_SERVICE_CLASS_SMS; |
|
4401 case 19: |
|
4402 return ICC_SERVICE_CLASS_FAX + ICC_SERVICE_CLASS_VOICE; |
|
4403 case 21: |
|
4404 return ICC_SERVICE_CLASS_PAD + ICC_SERVICE_CLASS_DATA_ASYNC; |
|
4405 case 22: |
|
4406 return ICC_SERVICE_CLASS_PACKET + ICC_SERVICE_CLASS_DATA_SYNC; |
|
4407 case 25: |
|
4408 return ICC_SERVICE_CLASS_DATA_ASYNC; |
|
4409 case 26: |
|
4410 return ICC_SERVICE_CLASS_DATA_SYNC + SERVICE_CLASS_VOICE; |
|
4411 case 99: |
|
4412 return ICC_SERVICE_CLASS_PACKET; |
|
4413 default: |
|
4414 return ICC_SERVICE_CLASS_NONE; |
|
4415 } |
|
4416 }, |
|
4417 |
|
4418 /** |
|
4419 * @param message A decoded SMS-DELIVER message. |
|
4420 * |
|
4421 * @see 3GPP TS 31.111 section 7.1.1 |
|
4422 */ |
|
4423 dataDownloadViaSMSPP: function(message) { |
|
4424 let Buf = this.context.Buf; |
|
4425 let GsmPDUHelper = this.context.GsmPDUHelper; |
|
4426 |
|
4427 let options = { |
|
4428 pid: message.pid, |
|
4429 dcs: message.dcs, |
|
4430 encoding: message.encoding, |
|
4431 }; |
|
4432 Buf.newParcel(REQUEST_STK_SEND_ENVELOPE_WITH_STATUS, options); |
|
4433 |
|
4434 Buf.seekIncoming(-1 * (Buf.getCurrentParcelSize() - Buf.getReadAvailable() |
|
4435 - 2 * Buf.UINT32_SIZE)); // Skip response_type & request_type. |
|
4436 let messageStringLength = Buf.readInt32(); // In semi-octets |
|
4437 let smscLength = GsmPDUHelper.readHexOctet(); // In octets, inclusive of TOA |
|
4438 let tpduLength = (messageStringLength / 2) - (smscLength + 1); // In octets |
|
4439 |
|
4440 // Device identities: 4 bytes |
|
4441 // Address: 0 or (2 + smscLength) |
|
4442 // SMS TPDU: (2 or 3) + tpduLength |
|
4443 let berLen = 4 + |
|
4444 (smscLength ? (2 + smscLength) : 0) + |
|
4445 (tpduLength <= 127 ? 2 : 3) + tpduLength; // In octets |
|
4446 |
|
4447 let parcelLength = (berLen <= 127 ? 2 : 3) + berLen; // In octets |
|
4448 Buf.writeInt32(parcelLength * 2); // In semi-octets |
|
4449 |
|
4450 // Write a BER-TLV |
|
4451 GsmPDUHelper.writeHexOctet(BER_SMS_PP_DOWNLOAD_TAG); |
|
4452 if (berLen > 127) { |
|
4453 GsmPDUHelper.writeHexOctet(0x81); |
|
4454 } |
|
4455 GsmPDUHelper.writeHexOctet(berLen); |
|
4456 |
|
4457 // Device Identifies-TLV |
|
4458 GsmPDUHelper.writeHexOctet(COMPREHENSIONTLV_TAG_DEVICE_ID | |
|
4459 COMPREHENSIONTLV_FLAG_CR); |
|
4460 GsmPDUHelper.writeHexOctet(0x02); |
|
4461 GsmPDUHelper.writeHexOctet(STK_DEVICE_ID_NETWORK); |
|
4462 GsmPDUHelper.writeHexOctet(STK_DEVICE_ID_SIM); |
|
4463 |
|
4464 // Address-TLV |
|
4465 if (smscLength) { |
|
4466 GsmPDUHelper.writeHexOctet(COMPREHENSIONTLV_TAG_ADDRESS); |
|
4467 GsmPDUHelper.writeHexOctet(smscLength); |
|
4468 Buf.copyIncomingToOutgoing(Buf.PDU_HEX_OCTET_SIZE * smscLength); |
|
4469 } |
|
4470 |
|
4471 // SMS TPDU-TLV |
|
4472 GsmPDUHelper.writeHexOctet(COMPREHENSIONTLV_TAG_SMS_TPDU | |
|
4473 COMPREHENSIONTLV_FLAG_CR); |
|
4474 if (tpduLength > 127) { |
|
4475 GsmPDUHelper.writeHexOctet(0x81); |
|
4476 } |
|
4477 GsmPDUHelper.writeHexOctet(tpduLength); |
|
4478 Buf.copyIncomingToOutgoing(Buf.PDU_HEX_OCTET_SIZE * tpduLength); |
|
4479 |
|
4480 // Write 2 string delimitors for the total string length must be even. |
|
4481 Buf.writeStringDelimiter(0); |
|
4482 |
|
4483 Buf.sendParcel(); |
|
4484 }, |
|
4485 |
|
4486 /** |
|
4487 * @param success A boolean value indicating the result of previous |
|
4488 * SMS-DELIVER message handling. |
|
4489 * @param responsePduLen ICC IO response PDU length in octets. |
|
4490 * @param options An object that contains four attributes: `pid`, `dcs`, |
|
4491 * `encoding` and `responsePduLen`. |
|
4492 * |
|
4493 * @see 3GPP TS 23.040 section 9.2.2.1a |
|
4494 */ |
|
4495 acknowledgeIncomingGsmSmsWithPDU: function(success, responsePduLen, options) { |
|
4496 let Buf = this.context.Buf; |
|
4497 let GsmPDUHelper = this.context.GsmPDUHelper; |
|
4498 |
|
4499 Buf.newParcel(REQUEST_ACKNOWLEDGE_INCOMING_GSM_SMS_WITH_PDU); |
|
4500 |
|
4501 // Two strings. |
|
4502 Buf.writeInt32(2); |
|
4503 |
|
4504 // String 1: Success |
|
4505 Buf.writeString(success ? "1" : "0"); |
|
4506 |
|
4507 // String 2: RP-ACK/RP-ERROR PDU |
|
4508 Buf.writeInt32(2 * (responsePduLen + (success ? 5 : 6))); // In semi-octet |
|
4509 // 1. TP-MTI & TP-UDHI |
|
4510 GsmPDUHelper.writeHexOctet(PDU_MTI_SMS_DELIVER); |
|
4511 if (!success) { |
|
4512 // 2. TP-FCS |
|
4513 GsmPDUHelper.writeHexOctet(PDU_FCS_USIM_DATA_DOWNLOAD_ERROR); |
|
4514 } |
|
4515 // 3. TP-PI |
|
4516 GsmPDUHelper.writeHexOctet(PDU_PI_USER_DATA_LENGTH | |
|
4517 PDU_PI_DATA_CODING_SCHEME | |
|
4518 PDU_PI_PROTOCOL_IDENTIFIER); |
|
4519 // 4. TP-PID |
|
4520 GsmPDUHelper.writeHexOctet(options.pid); |
|
4521 // 5. TP-DCS |
|
4522 GsmPDUHelper.writeHexOctet(options.dcs); |
|
4523 // 6. TP-UDL |
|
4524 if (options.encoding == PDU_DCS_MSG_CODING_7BITS_ALPHABET) { |
|
4525 GsmPDUHelper.writeHexOctet(Math.floor(responsePduLen * 8 / 7)); |
|
4526 } else { |
|
4527 GsmPDUHelper.writeHexOctet(responsePduLen); |
|
4528 } |
|
4529 // TP-UD |
|
4530 Buf.copyIncomingToOutgoing(Buf.PDU_HEX_OCTET_SIZE * responsePduLen); |
|
4531 // Write 2 string delimitors for the total string length must be even. |
|
4532 Buf.writeStringDelimiter(0); |
|
4533 |
|
4534 Buf.sendParcel(); |
|
4535 }, |
|
4536 |
|
4537 /** |
|
4538 * @param message A decoded SMS-DELIVER message. |
|
4539 */ |
|
4540 writeSmsToSIM: function(message) { |
|
4541 let Buf = this.context.Buf; |
|
4542 let GsmPDUHelper = this.context.GsmPDUHelper; |
|
4543 |
|
4544 Buf.newParcel(REQUEST_WRITE_SMS_TO_SIM); |
|
4545 |
|
4546 // Write EFsms Status |
|
4547 Buf.writeInt32(EFSMS_STATUS_FREE); |
|
4548 |
|
4549 Buf.seekIncoming(-1 * (Buf.getCurrentParcelSize() - Buf.getReadAvailable() |
|
4550 - 2 * Buf.UINT32_SIZE)); // Skip response_type & request_type. |
|
4551 let messageStringLength = Buf.readInt32(); // In semi-octets |
|
4552 let smscLength = GsmPDUHelper.readHexOctet(); // In octets, inclusive of TOA |
|
4553 let pduLength = (messageStringLength / 2) - (smscLength + 1); // In octets |
|
4554 |
|
4555 // 1. Write PDU first. |
|
4556 if (smscLength > 0) { |
|
4557 Buf.seekIncoming(smscLength * Buf.PDU_HEX_OCTET_SIZE); |
|
4558 } |
|
4559 // Write EFsms PDU string length |
|
4560 Buf.writeInt32(2 * pduLength); // In semi-octets |
|
4561 if (pduLength) { |
|
4562 Buf.copyIncomingToOutgoing(Buf.PDU_HEX_OCTET_SIZE * pduLength); |
|
4563 } |
|
4564 // Write 2 string delimitors for the total string length must be even. |
|
4565 Buf.writeStringDelimiter(0); |
|
4566 |
|
4567 // 2. Write SMSC |
|
4568 // Write EFsms SMSC string length |
|
4569 Buf.writeInt32(2 * (smscLength + 1)); // Plus smscLength itself, in semi-octets |
|
4570 // Write smscLength |
|
4571 GsmPDUHelper.writeHexOctet(smscLength); |
|
4572 // Write TOA & SMSC Address |
|
4573 if (smscLength) { |
|
4574 Buf.seekIncoming(-1 * (Buf.getCurrentParcelSize() - Buf.getReadAvailable() |
|
4575 - 2 * Buf.UINT32_SIZE // Skip response_type, request_type. |
|
4576 - 2 * Buf.PDU_HEX_OCTET_SIZE)); // Skip messageStringLength & smscLength. |
|
4577 Buf.copyIncomingToOutgoing(Buf.PDU_HEX_OCTET_SIZE * smscLength); |
|
4578 } |
|
4579 // Write 2 string delimitors for the total string length must be even. |
|
4580 Buf.writeStringDelimiter(0); |
|
4581 |
|
4582 Buf.sendParcel(); |
|
4583 }, |
|
4584 |
|
4585 /** |
|
4586 * Helper to delegate the received sms segment to RadioInterface to process. |
|
4587 * |
|
4588 * @param message |
|
4589 * Received sms message. |
|
4590 * |
|
4591 * @return MOZ_FCS_WAIT_FOR_EXPLICIT_ACK |
|
4592 */ |
|
4593 _processSmsMultipart: function(message) { |
|
4594 message.rilMessageType = "sms-received"; |
|
4595 |
|
4596 this.sendChromeMessage(message); |
|
4597 |
|
4598 return MOZ_FCS_WAIT_FOR_EXPLICIT_ACK; |
|
4599 }, |
|
4600 |
|
4601 /** |
|
4602 * Helper for processing SMS-STATUS-REPORT PDUs. |
|
4603 * |
|
4604 * @param length |
|
4605 * Length of SMS string in the incoming parcel. |
|
4606 * |
|
4607 * @return A failure cause defined in 3GPP 23.040 clause 9.2.3.22. |
|
4608 */ |
|
4609 _processSmsStatusReport: function(length) { |
|
4610 let [message, result] = this.context.GsmPDUHelper.processReceivedSms(length); |
|
4611 if (!message) { |
|
4612 if (DEBUG) this.context.debug("invalid SMS-STATUS-REPORT"); |
|
4613 return PDU_FCS_UNSPECIFIED; |
|
4614 } |
|
4615 |
|
4616 let options = this._pendingSentSmsMap[message.messageRef]; |
|
4617 if (!options) { |
|
4618 if (DEBUG) this.context.debug("no pending SMS-SUBMIT message"); |
|
4619 return PDU_FCS_OK; |
|
4620 } |
|
4621 |
|
4622 let status = message.status; |
|
4623 |
|
4624 // 3GPP TS 23.040 9.2.3.15 `The MS shall interpret any reserved values as |
|
4625 // "Service Rejected"(01100011) but shall store them exactly as received.` |
|
4626 if ((status >= 0x80) |
|
4627 || ((status >= PDU_ST_0_RESERVED_BEGIN) |
|
4628 && (status < PDU_ST_0_SC_SPECIFIC_BEGIN)) |
|
4629 || ((status >= PDU_ST_1_RESERVED_BEGIN) |
|
4630 && (status < PDU_ST_1_SC_SPECIFIC_BEGIN)) |
|
4631 || ((status >= PDU_ST_2_RESERVED_BEGIN) |
|
4632 && (status < PDU_ST_2_SC_SPECIFIC_BEGIN)) |
|
4633 || ((status >= PDU_ST_3_RESERVED_BEGIN) |
|
4634 && (status < PDU_ST_3_SC_SPECIFIC_BEGIN)) |
|
4635 ) { |
|
4636 status = PDU_ST_3_SERVICE_REJECTED; |
|
4637 } |
|
4638 |
|
4639 // Pending. Waiting for next status report. |
|
4640 if ((status >>> 5) == 0x01) { |
|
4641 if (DEBUG) this.context.debug("SMS-STATUS-REPORT: delivery still pending"); |
|
4642 return PDU_FCS_OK; |
|
4643 } |
|
4644 |
|
4645 delete this._pendingSentSmsMap[message.messageRef]; |
|
4646 |
|
4647 let deliveryStatus = ((status >>> 5) === 0x00) |
|
4648 ? GECKO_SMS_DELIVERY_STATUS_SUCCESS |
|
4649 : GECKO_SMS_DELIVERY_STATUS_ERROR; |
|
4650 this.sendChromeMessage({ |
|
4651 rilMessageType: options.rilMessageType, |
|
4652 rilMessageToken: options.rilMessageToken, |
|
4653 deliveryStatus: deliveryStatus |
|
4654 }); |
|
4655 |
|
4656 return PDU_FCS_OK; |
|
4657 }, |
|
4658 |
|
4659 /** |
|
4660 * Helper for processing CDMA SMS Delivery Acknowledgment Message |
|
4661 * |
|
4662 * @param message |
|
4663 * decoded SMS Delivery ACK message from CdmaPDUHelper. |
|
4664 * |
|
4665 * @return A failure cause defined in 3GPP 23.040 clause 9.2.3.22. |
|
4666 */ |
|
4667 _processCdmaSmsStatusReport: function(message) { |
|
4668 let options = this._pendingSentSmsMap[message.msgId]; |
|
4669 if (!options) { |
|
4670 if (DEBUG) this.context.debug("no pending SMS-SUBMIT message"); |
|
4671 return PDU_FCS_OK; |
|
4672 } |
|
4673 |
|
4674 if (message.errorClass === 2) { |
|
4675 if (DEBUG) { |
|
4676 this.context.debug("SMS-STATUS-REPORT: delivery still pending, " + |
|
4677 "msgStatus: " + message.msgStatus); |
|
4678 } |
|
4679 return PDU_FCS_OK; |
|
4680 } |
|
4681 |
|
4682 delete this._pendingSentSmsMap[message.msgId]; |
|
4683 |
|
4684 if (message.errorClass === -1 && message.body) { |
|
4685 // Process as normal incoming SMS, if errorClass is invalid |
|
4686 // but message body is available. |
|
4687 return this._processSmsMultipart(message); |
|
4688 } |
|
4689 |
|
4690 let deliveryStatus = (message.errorClass === 0) |
|
4691 ? GECKO_SMS_DELIVERY_STATUS_SUCCESS |
|
4692 : GECKO_SMS_DELIVERY_STATUS_ERROR; |
|
4693 this.sendChromeMessage({ |
|
4694 rilMessageType: options.rilMessageType, |
|
4695 rilMessageToken: options.rilMessageToken, |
|
4696 deliveryStatus: deliveryStatus |
|
4697 }); |
|
4698 |
|
4699 return PDU_FCS_OK; |
|
4700 }, |
|
4701 |
|
4702 /** |
|
4703 * Helper for processing CDMA SMS WAP Push Message |
|
4704 * |
|
4705 * @param message |
|
4706 * decoded WAP message from CdmaPDUHelper. |
|
4707 * |
|
4708 * @return A failure cause defined in 3GPP 23.040 clause 9.2.3.22. |
|
4709 */ |
|
4710 _processCdmaSmsWapPush: function(message) { |
|
4711 if (!message.data) { |
|
4712 if (DEBUG) this.context.debug("no data inside WAP Push message."); |
|
4713 return PDU_FCS_OK; |
|
4714 } |
|
4715 |
|
4716 // See 6.5. MAPPING OF WDP TO CDMA SMS in WAP-295-WDP. |
|
4717 // |
|
4718 // Field | Length (bits) |
|
4719 // ----------------------------------------- |
|
4720 // MSG_TYPE | 8 |
|
4721 // TOTAL_SEGMENTS | 8 |
|
4722 // SEGMENT_NUMBER | 8 |
|
4723 // DATAGRAM | (NUM_FIELDS – 3) * 8 |
|
4724 let index = 0; |
|
4725 if (message.data[index++] !== 0) { |
|
4726 if (DEBUG) this.context.debug("Ignore a WAP Message which is not WDP."); |
|
4727 return PDU_FCS_OK; |
|
4728 } |
|
4729 |
|
4730 // 1. Originator Address in SMS-TL + Message_Id in SMS-TS are used to identify a unique WDP datagram. |
|
4731 // 2. TOTAL_SEGMENTS, SEGMENT_NUMBER are used to verify that a complete |
|
4732 // datagram has been received and is ready to be passed to a higher layer. |
|
4733 message.header = { |
|
4734 segmentRef: message.msgId, |
|
4735 segmentMaxSeq: message.data[index++], |
|
4736 segmentSeq: message.data[index++] + 1 // It's zero-based in CDMA WAP Push. |
|
4737 }; |
|
4738 |
|
4739 if (message.header.segmentSeq > message.header.segmentMaxSeq) { |
|
4740 if (DEBUG) this.context.debug("Wrong WDP segment info."); |
|
4741 return PDU_FCS_OK; |
|
4742 } |
|
4743 |
|
4744 // Ports are only specified in 1st segment. |
|
4745 if (message.header.segmentSeq == 1) { |
|
4746 message.header.originatorPort = message.data[index++] << 8; |
|
4747 message.header.originatorPort |= message.data[index++]; |
|
4748 message.header.destinationPort = message.data[index++] << 8; |
|
4749 message.header.destinationPort |= message.data[index++]; |
|
4750 } |
|
4751 |
|
4752 message.data = message.data.subarray(index); |
|
4753 |
|
4754 return this._processSmsMultipart(message); |
|
4755 }, |
|
4756 |
|
4757 /** |
|
4758 * Helper for processing sent multipart SMS. |
|
4759 */ |
|
4760 _processSentSmsSegment: function(options) { |
|
4761 // Setup attributes for sending next segment |
|
4762 let next = options.segmentSeq; |
|
4763 options.body = options.segments[next].body; |
|
4764 options.encodedBodyLength = options.segments[next].encodedBodyLength; |
|
4765 options.segmentSeq = next + 1; |
|
4766 |
|
4767 this.sendSMS(options); |
|
4768 }, |
|
4769 |
|
4770 /** |
|
4771 * Helper for processing result of send SMS. |
|
4772 * |
|
4773 * @param length |
|
4774 * Length of SMS string in the incoming parcel. |
|
4775 * @param options |
|
4776 * Sms information. |
|
4777 */ |
|
4778 _processSmsSendResult: function(length, options) { |
|
4779 if (options.rilRequestError) { |
|
4780 if (DEBUG) { |
|
4781 this.context.debug("_processSmsSendResult: rilRequestError = " + |
|
4782 options.rilRequestError); |
|
4783 } |
|
4784 switch (options.rilRequestError) { |
|
4785 case ERROR_SMS_SEND_FAIL_RETRY: |
|
4786 if (options.retryCount < SMS_RETRY_MAX) { |
|
4787 options.retryCount++; |
|
4788 // TODO: bug 736702 TP-MR, retry interval, retry timeout |
|
4789 this.sendSMS(options); |
|
4790 break; |
|
4791 } |
|
4792 // Fallback to default error handling if it meets max retry count. |
|
4793 // Fall through. |
|
4794 default: |
|
4795 this.sendChromeMessage({ |
|
4796 rilMessageType: options.rilMessageType, |
|
4797 rilMessageToken: options.rilMessageToken, |
|
4798 errorMsg: options.rilRequestError, |
|
4799 }); |
|
4800 break; |
|
4801 } |
|
4802 return; |
|
4803 } |
|
4804 |
|
4805 let Buf = this.context.Buf; |
|
4806 options.messageRef = Buf.readInt32(); |
|
4807 options.ackPDU = Buf.readString(); |
|
4808 options.errorCode = Buf.readInt32(); |
|
4809 |
|
4810 if ((options.segmentMaxSeq > 1) |
|
4811 && (options.segmentSeq < options.segmentMaxSeq)) { |
|
4812 // Not last segment |
|
4813 this._processSentSmsSegment(options); |
|
4814 } else { |
|
4815 // Last segment sent with success. |
|
4816 if (options.requestStatusReport) { |
|
4817 if (DEBUG) { |
|
4818 this.context.debug("waiting SMS-STATUS-REPORT for messageRef " + |
|
4819 options.messageRef); |
|
4820 } |
|
4821 this._pendingSentSmsMap[options.messageRef] = options; |
|
4822 } |
|
4823 |
|
4824 this.sendChromeMessage({ |
|
4825 rilMessageType: options.rilMessageType, |
|
4826 rilMessageToken: options.rilMessageToken, |
|
4827 }); |
|
4828 } |
|
4829 }, |
|
4830 |
|
4831 _processReceivedSmsCbPage: function(original) { |
|
4832 if (original.numPages <= 1) { |
|
4833 if (original.body) { |
|
4834 original.fullBody = original.body; |
|
4835 delete original.body; |
|
4836 } else if (original.data) { |
|
4837 original.fullData = original.data; |
|
4838 delete original.data; |
|
4839 } |
|
4840 return original; |
|
4841 } |
|
4842 |
|
4843 // Hash = <serial>:<mcc>:<mnc>:<lac>:<cid> |
|
4844 let hash = original.serial + ":" + this.iccInfo.mcc + ":" |
|
4845 + this.iccInfo.mnc + ":"; |
|
4846 switch (original.geographicalScope) { |
|
4847 case CB_GSM_GEOGRAPHICAL_SCOPE_CELL_WIDE_IMMEDIATE: |
|
4848 case CB_GSM_GEOGRAPHICAL_SCOPE_CELL_WIDE: |
|
4849 hash += this.voiceRegistrationState.cell.gsmLocationAreaCode + ":" |
|
4850 + this.voiceRegistrationState.cell.gsmCellId; |
|
4851 break; |
|
4852 case CB_GSM_GEOGRAPHICAL_SCOPE_LOCATION_AREA_WIDE: |
|
4853 hash += this.voiceRegistrationState.cell.gsmLocationAreaCode + ":"; |
|
4854 break; |
|
4855 default: |
|
4856 hash += ":"; |
|
4857 break; |
|
4858 } |
|
4859 |
|
4860 let index = original.pageIndex; |
|
4861 |
|
4862 let options = this._receivedSmsCbPagesMap[hash]; |
|
4863 if (!options) { |
|
4864 options = original; |
|
4865 this._receivedSmsCbPagesMap[hash] = options; |
|
4866 |
|
4867 options.receivedPages = 0; |
|
4868 options.pages = []; |
|
4869 } else if (options.pages[index]) { |
|
4870 // Duplicated page? |
|
4871 if (DEBUG) { |
|
4872 this.context.debug("Got duplicated page no." + index + |
|
4873 " of a multipage SMSCB: " + JSON.stringify(original)); |
|
4874 } |
|
4875 return null; |
|
4876 } |
|
4877 |
|
4878 if (options.encoding == PDU_DCS_MSG_CODING_8BITS_ALPHABET) { |
|
4879 options.pages[index] = original.data; |
|
4880 delete original.data; |
|
4881 } else { |
|
4882 options.pages[index] = original.body; |
|
4883 delete original.body; |
|
4884 } |
|
4885 options.receivedPages++; |
|
4886 if (options.receivedPages < options.numPages) { |
|
4887 if (DEBUG) { |
|
4888 this.context.debug("Got page no." + index + " of a multipage SMSCB: " + |
|
4889 JSON.stringify(options)); |
|
4890 } |
|
4891 return null; |
|
4892 } |
|
4893 |
|
4894 // Remove from map |
|
4895 delete this._receivedSmsCbPagesMap[hash]; |
|
4896 |
|
4897 // Rebuild full body |
|
4898 if (options.encoding == PDU_DCS_MSG_CODING_8BITS_ALPHABET) { |
|
4899 // Uint8Array doesn't have `concat`, so we have to merge all pages by hand. |
|
4900 let fullDataLen = 0; |
|
4901 for (let i = 1; i <= options.numPages; i++) { |
|
4902 fullDataLen += options.pages[i].length; |
|
4903 } |
|
4904 |
|
4905 options.fullData = new Uint8Array(fullDataLen); |
|
4906 for (let d= 0, i = 1; i <= options.numPages; i++) { |
|
4907 let data = options.pages[i]; |
|
4908 for (let j = 0; j < data.length; j++) { |
|
4909 options.fullData[d++] = data[j]; |
|
4910 } |
|
4911 } |
|
4912 } else { |
|
4913 options.fullBody = options.pages.join(""); |
|
4914 } |
|
4915 |
|
4916 if (DEBUG) { |
|
4917 this.context.debug("Got full multipage SMSCB: " + JSON.stringify(options)); |
|
4918 } |
|
4919 |
|
4920 return options; |
|
4921 }, |
|
4922 |
|
4923 _mergeCellBroadcastConfigs: function(list, from, to) { |
|
4924 if (!list) { |
|
4925 return [from, to]; |
|
4926 } |
|
4927 |
|
4928 for (let i = 0, f1, t1; i < list.length;) { |
|
4929 f1 = list[i++]; |
|
4930 t1 = list[i++]; |
|
4931 if (to == f1) { |
|
4932 // ...[from]...[to|f1]...(t1) |
|
4933 list[i - 2] = from; |
|
4934 return list; |
|
4935 } |
|
4936 |
|
4937 if (to < f1) { |
|
4938 // ...[from]...(to)...[f1] or ...[from]...(to)[f1] |
|
4939 if (i > 2) { |
|
4940 // Not the first range pair, merge three arrays. |
|
4941 return list.slice(0, i - 2).concat([from, to]).concat(list.slice(i - 2)); |
|
4942 } else { |
|
4943 return [from, to].concat(list); |
|
4944 } |
|
4945 } |
|
4946 |
|
4947 if (from > t1) { |
|
4948 // ...[f1]...(t1)[from] or ...[f1]...(t1)...[from] |
|
4949 continue; |
|
4950 } |
|
4951 |
|
4952 // Have overlap or merge-able adjacency with [f1]...(t1). Replace it |
|
4953 // with [min(from, f1)]...(max(to, t1)). |
|
4954 |
|
4955 let changed = false; |
|
4956 if (from < f1) { |
|
4957 // [from]...[f1]...(t1) or [from][f1]...(t1) |
|
4958 // Save minimum from value. |
|
4959 list[i - 2] = from; |
|
4960 changed = true; |
|
4961 } |
|
4962 |
|
4963 if (to <= t1) { |
|
4964 // [from]...[to](t1) or [from]...(to|t1) |
|
4965 // Can't have further merge-able adjacency. Return. |
|
4966 return list; |
|
4967 } |
|
4968 |
|
4969 // Try merging possible next adjacent range. |
|
4970 let j = i; |
|
4971 for (let f2, t2; j < list.length;) { |
|
4972 f2 = list[j++]; |
|
4973 t2 = list[j++]; |
|
4974 if (to > t2) { |
|
4975 // [from]...[f2]...[t2]...(to) or [from]...[f2]...[t2](to) |
|
4976 // Merge next adjacent range again. |
|
4977 continue; |
|
4978 } |
|
4979 |
|
4980 if (to < t2) { |
|
4981 if (to < f2) { |
|
4982 // [from]...(to)[f2] or [from]...(to)...[f2] |
|
4983 // Roll back and give up. |
|
4984 j -= 2; |
|
4985 } else if (to < t2) { |
|
4986 // [from]...[to|f2]...(t2), or [from]...[f2]...[to](t2) |
|
4987 // Merge to [from]...(t2) and give up. |
|
4988 to = t2; |
|
4989 } |
|
4990 } |
|
4991 |
|
4992 break; |
|
4993 } |
|
4994 |
|
4995 // Save maximum to value. |
|
4996 list[i - 1] = to; |
|
4997 |
|
4998 if (j != i) { |
|
4999 // Remove merged adjacent ranges. |
|
5000 let ret = list.slice(0, i); |
|
5001 if (j < list.length) { |
|
5002 ret = ret.concat(list.slice(j)); |
|
5003 } |
|
5004 return ret; |
|
5005 } |
|
5006 |
|
5007 return list; |
|
5008 } |
|
5009 |
|
5010 // Append to the end. |
|
5011 list.push(from); |
|
5012 list.push(to); |
|
5013 |
|
5014 return list; |
|
5015 }, |
|
5016 |
|
5017 _isCellBroadcastConfigReady: function() { |
|
5018 if (!("MMI" in this.cellBroadcastConfigs)) { |
|
5019 return false; |
|
5020 } |
|
5021 |
|
5022 // CBMI should be ready in GSM. |
|
5023 if (!this._isCdma && |
|
5024 (!("CBMI" in this.cellBroadcastConfigs) || |
|
5025 !("CBMID" in this.cellBroadcastConfigs) || |
|
5026 !("CBMIR" in this.cellBroadcastConfigs))) { |
|
5027 return false; |
|
5028 } |
|
5029 |
|
5030 return true; |
|
5031 }, |
|
5032 |
|
5033 /** |
|
5034 * Merge all members of cellBroadcastConfigs into mergedCellBroadcastConfig. |
|
5035 */ |
|
5036 _mergeAllCellBroadcastConfigs: function() { |
|
5037 if (!this._isCellBroadcastConfigReady()) { |
|
5038 if (DEBUG) { |
|
5039 this.context.debug("cell broadcast configs not ready, waiting ..."); |
|
5040 } |
|
5041 return; |
|
5042 } |
|
5043 |
|
5044 // Prepare cell broadcast config. CBMI* are only used in GSM. |
|
5045 let usedCellBroadcastConfigs = {MMI: this.cellBroadcastConfigs.MMI}; |
|
5046 if (!this._isCdma) { |
|
5047 usedCellBroadcastConfigs.CBMI = this.cellBroadcastConfigs.CBMI; |
|
5048 usedCellBroadcastConfigs.CBMID = this.cellBroadcastConfigs.CBMID; |
|
5049 usedCellBroadcastConfigs.CBMIR = this.cellBroadcastConfigs.CBMIR; |
|
5050 } |
|
5051 |
|
5052 if (DEBUG) { |
|
5053 this.context.debug("Cell Broadcast search lists: " + |
|
5054 JSON.stringify(usedCellBroadcastConfigs)); |
|
5055 } |
|
5056 |
|
5057 let list = null; |
|
5058 for each (let ll in usedCellBroadcastConfigs) { |
|
5059 if (ll == null) { |
|
5060 continue; |
|
5061 } |
|
5062 |
|
5063 for (let i = 0; i < ll.length; i += 2) { |
|
5064 list = this._mergeCellBroadcastConfigs(list, ll[i], ll[i + 1]); |
|
5065 } |
|
5066 } |
|
5067 |
|
5068 if (DEBUG) { |
|
5069 this.context.debug("Cell Broadcast search lists(merged): " + |
|
5070 JSON.stringify(list)); |
|
5071 } |
|
5072 this.mergedCellBroadcastConfig = list; |
|
5073 this.updateCellBroadcastConfig(); |
|
5074 }, |
|
5075 |
|
5076 /** |
|
5077 * Check whether search list from settings is settable by MMI, that is, |
|
5078 * whether the range is bounded in any entries of CB_NON_MMI_SETTABLE_RANGES. |
|
5079 */ |
|
5080 _checkCellBroadcastMMISettable: function(from, to) { |
|
5081 if ((to <= from) || (from >= 65536) || (from < 0)) { |
|
5082 return false; |
|
5083 } |
|
5084 |
|
5085 if (!this._isCdma) { |
|
5086 // GSM not settable ranges. |
|
5087 for (let i = 0, f, t; i < CB_NON_MMI_SETTABLE_RANGES.length;) { |
|
5088 f = CB_NON_MMI_SETTABLE_RANGES[i++]; |
|
5089 t = CB_NON_MMI_SETTABLE_RANGES[i++]; |
|
5090 if ((from < t) && (to > f)) { |
|
5091 // Have overlap. |
|
5092 return false; |
|
5093 } |
|
5094 } |
|
5095 } |
|
5096 |
|
5097 return true; |
|
5098 }, |
|
5099 |
|
5100 /** |
|
5101 * Convert Cell Broadcast settings string into search list. |
|
5102 */ |
|
5103 _convertCellBroadcastSearchList: function(searchListStr) { |
|
5104 let parts = searchListStr && searchListStr.split(","); |
|
5105 if (!parts) { |
|
5106 return null; |
|
5107 } |
|
5108 |
|
5109 let list = null; |
|
5110 let result, from, to; |
|
5111 for (let range of parts) { |
|
5112 // Match "12" or "12-34". The result will be ["12", "12", null] or |
|
5113 // ["12-34", "12", "34"]. |
|
5114 result = range.match(/^(\d+)(?:-(\d+))?$/); |
|
5115 if (!result) { |
|
5116 throw "Invalid format"; |
|
5117 } |
|
5118 |
|
5119 from = parseInt(result[1], 10); |
|
5120 to = (result[2]) ? parseInt(result[2], 10) + 1 : from + 1; |
|
5121 if (!this._checkCellBroadcastMMISettable(from, to)) { |
|
5122 throw "Invalid range"; |
|
5123 } |
|
5124 |
|
5125 if (list == null) { |
|
5126 list = []; |
|
5127 } |
|
5128 list.push(from); |
|
5129 list.push(to); |
|
5130 } |
|
5131 |
|
5132 return list; |
|
5133 }, |
|
5134 |
|
5135 /** |
|
5136 * Handle incoming messages from the main UI thread. |
|
5137 * |
|
5138 * @param message |
|
5139 * Object containing the message. Messages are supposed |
|
5140 */ |
|
5141 handleChromeMessage: function(message) { |
|
5142 if (DEBUG) { |
|
5143 this.context.debug("Received chrome message " + JSON.stringify(message)); |
|
5144 } |
|
5145 let method = this[message.rilMessageType]; |
|
5146 if (typeof method != "function") { |
|
5147 if (DEBUG) { |
|
5148 this.context.debug("Don't know what to do with message " + |
|
5149 JSON.stringify(message)); |
|
5150 } |
|
5151 return; |
|
5152 } |
|
5153 method.call(this, message); |
|
5154 }, |
|
5155 |
|
5156 /** |
|
5157 * Get a list of current voice calls. |
|
5158 */ |
|
5159 enumerateCalls: function(options) { |
|
5160 if (DEBUG) this.context.debug("Sending all current calls"); |
|
5161 let calls = []; |
|
5162 for each (let call in this.currentCalls) { |
|
5163 calls.push(call); |
|
5164 } |
|
5165 options.calls = calls; |
|
5166 this.sendChromeMessage(options); |
|
5167 }, |
|
5168 |
|
5169 /** |
|
5170 * Process STK Proactive Command. |
|
5171 */ |
|
5172 processStkProactiveCommand: function() { |
|
5173 let Buf = this.context.Buf; |
|
5174 let length = Buf.readInt32(); |
|
5175 let berTlv; |
|
5176 try { |
|
5177 berTlv = this.context.BerTlvHelper.decode(length / 2); |
|
5178 } catch (e) { |
|
5179 if (DEBUG) this.context.debug("processStkProactiveCommand : " + e); |
|
5180 this.sendStkTerminalResponse({ |
|
5181 resultCode: STK_RESULT_CMD_DATA_NOT_UNDERSTOOD}); |
|
5182 return; |
|
5183 } |
|
5184 |
|
5185 Buf.readStringDelimiter(length); |
|
5186 |
|
5187 let ctlvs = berTlv.value; |
|
5188 let ctlv = this.context.StkProactiveCmdHelper.searchForTag( |
|
5189 COMPREHENSIONTLV_TAG_COMMAND_DETAILS, ctlvs); |
|
5190 if (!ctlv) { |
|
5191 this.sendStkTerminalResponse({ |
|
5192 resultCode: STK_RESULT_CMD_DATA_NOT_UNDERSTOOD}); |
|
5193 throw new Error("Can't find COMMAND_DETAILS ComprehensionTlv"); |
|
5194 } |
|
5195 |
|
5196 let cmdDetails = ctlv.value; |
|
5197 if (DEBUG) { |
|
5198 this.context.debug("commandNumber = " + cmdDetails.commandNumber + |
|
5199 " typeOfCommand = " + cmdDetails.typeOfCommand.toString(16) + |
|
5200 " commandQualifier = " + cmdDetails.commandQualifier); |
|
5201 } |
|
5202 |
|
5203 // STK_CMD_MORE_TIME need not to propagate event to chrome. |
|
5204 if (cmdDetails.typeOfCommand == STK_CMD_MORE_TIME) { |
|
5205 this.sendStkTerminalResponse({ |
|
5206 command: cmdDetails, |
|
5207 resultCode: STK_RESULT_OK}); |
|
5208 return; |
|
5209 } |
|
5210 |
|
5211 cmdDetails.rilMessageType = "stkcommand"; |
|
5212 cmdDetails.options = |
|
5213 this.context.StkCommandParamsFactory.createParam(cmdDetails, ctlvs); |
|
5214 this.sendChromeMessage(cmdDetails); |
|
5215 }, |
|
5216 |
|
5217 /** |
|
5218 * Send messages to the main thread. |
|
5219 */ |
|
5220 sendChromeMessage: function(message) { |
|
5221 message.rilMessageClientId = this.context.clientId; |
|
5222 postMessage(message); |
|
5223 }, |
|
5224 |
|
5225 /** |
|
5226 * Handle incoming requests from the RIL. We find the method that |
|
5227 * corresponds to the request type. Incidentally, the request type |
|
5228 * _is_ the method name, so that's easy. |
|
5229 */ |
|
5230 |
|
5231 handleParcel: function(request_type, length, options) { |
|
5232 let method = this[request_type]; |
|
5233 if (typeof method == "function") { |
|
5234 if (DEBUG) this.context.debug("Handling parcel as " + method.name); |
|
5235 method.call(this, length, options); |
|
5236 } |
|
5237 } |
|
5238 }; |
|
5239 |
|
5240 RilObject.prototype[REQUEST_GET_SIM_STATUS] = function REQUEST_GET_SIM_STATUS(length, options) { |
|
5241 if (options.rilRequestError) { |
|
5242 return; |
|
5243 } |
|
5244 |
|
5245 let iccStatus = {}; |
|
5246 let Buf = this.context.Buf; |
|
5247 iccStatus.cardState = Buf.readInt32(); // CARD_STATE_* |
|
5248 iccStatus.universalPINState = Buf.readInt32(); // CARD_PINSTATE_* |
|
5249 iccStatus.gsmUmtsSubscriptionAppIndex = Buf.readInt32(); |
|
5250 iccStatus.cdmaSubscriptionAppIndex = Buf.readInt32(); |
|
5251 if (!this.v5Legacy) { |
|
5252 iccStatus.imsSubscriptionAppIndex = Buf.readInt32(); |
|
5253 } |
|
5254 |
|
5255 let apps_length = Buf.readInt32(); |
|
5256 if (apps_length > CARD_MAX_APPS) { |
|
5257 apps_length = CARD_MAX_APPS; |
|
5258 } |
|
5259 |
|
5260 iccStatus.apps = []; |
|
5261 for (let i = 0 ; i < apps_length ; i++) { |
|
5262 iccStatus.apps.push({ |
|
5263 app_type: Buf.readInt32(), // CARD_APPTYPE_* |
|
5264 app_state: Buf.readInt32(), // CARD_APPSTATE_* |
|
5265 perso_substate: Buf.readInt32(), // CARD_PERSOSUBSTATE_* |
|
5266 aid: Buf.readString(), |
|
5267 app_label: Buf.readString(), |
|
5268 pin1_replaced: Buf.readInt32(), |
|
5269 pin1: Buf.readInt32(), |
|
5270 pin2: Buf.readInt32() |
|
5271 }); |
|
5272 if (RILQUIRKS_SIM_APP_STATE_EXTRA_FIELDS) { |
|
5273 Buf.readInt32(); |
|
5274 Buf.readInt32(); |
|
5275 Buf.readInt32(); |
|
5276 Buf.readInt32(); |
|
5277 } |
|
5278 } |
|
5279 |
|
5280 if (DEBUG) this.context.debug("iccStatus: " + JSON.stringify(iccStatus)); |
|
5281 this._processICCStatus(iccStatus); |
|
5282 }; |
|
5283 RilObject.prototype[REQUEST_ENTER_SIM_PIN] = function REQUEST_ENTER_SIM_PIN(length, options) { |
|
5284 this._processEnterAndChangeICCResponses(length, options); |
|
5285 }; |
|
5286 RilObject.prototype[REQUEST_ENTER_SIM_PUK] = function REQUEST_ENTER_SIM_PUK(length, options) { |
|
5287 this._processEnterAndChangeICCResponses(length, options); |
|
5288 }; |
|
5289 RilObject.prototype[REQUEST_ENTER_SIM_PIN2] = function REQUEST_ENTER_SIM_PIN2(length, options) { |
|
5290 this._processEnterAndChangeICCResponses(length, options); |
|
5291 }; |
|
5292 RilObject.prototype[REQUEST_ENTER_SIM_PUK2] = function REQUEST_ENTER_SIM_PUK(length, options) { |
|
5293 this._processEnterAndChangeICCResponses(length, options); |
|
5294 }; |
|
5295 RilObject.prototype[REQUEST_CHANGE_SIM_PIN] = function REQUEST_CHANGE_SIM_PIN(length, options) { |
|
5296 this._processEnterAndChangeICCResponses(length, options); |
|
5297 }; |
|
5298 RilObject.prototype[REQUEST_CHANGE_SIM_PIN2] = function REQUEST_CHANGE_SIM_PIN2(length, options) { |
|
5299 this._processEnterAndChangeICCResponses(length, options); |
|
5300 }; |
|
5301 RilObject.prototype[REQUEST_ENTER_NETWORK_DEPERSONALIZATION_CODE] = |
|
5302 function REQUEST_ENTER_NETWORK_DEPERSONALIZATION_CODE(length, options) { |
|
5303 this._processEnterAndChangeICCResponses(length, options); |
|
5304 }; |
|
5305 RilObject.prototype[REQUEST_GET_CURRENT_CALLS] = function REQUEST_GET_CURRENT_CALLS(length, options) { |
|
5306 if (options.rilRequestError) { |
|
5307 return; |
|
5308 } |
|
5309 |
|
5310 let Buf = this.context.Buf; |
|
5311 let calls_length = 0; |
|
5312 // The RIL won't even send us the length integer if there are no active calls. |
|
5313 // So only read this integer if the parcel actually has it. |
|
5314 if (length) { |
|
5315 calls_length = Buf.readInt32(); |
|
5316 } |
|
5317 if (!calls_length) { |
|
5318 this._processCalls(null); |
|
5319 return; |
|
5320 } |
|
5321 |
|
5322 let calls = {}; |
|
5323 for (let i = 0; i < calls_length; i++) { |
|
5324 let call = {}; |
|
5325 |
|
5326 // Extra uint32 field to get correct callIndex and rest of call data for |
|
5327 // call waiting feature. |
|
5328 if (RILQUIRKS_EXTRA_UINT32_2ND_CALL && i > 0) { |
|
5329 Buf.readInt32(); |
|
5330 } |
|
5331 |
|
5332 call.state = Buf.readInt32(); // CALL_STATE_* |
|
5333 call.callIndex = Buf.readInt32(); // GSM index (1-based) |
|
5334 call.toa = Buf.readInt32(); |
|
5335 call.isMpty = Boolean(Buf.readInt32()); |
|
5336 call.isMT = Boolean(Buf.readInt32()); |
|
5337 call.als = Buf.readInt32(); |
|
5338 call.isVoice = Boolean(Buf.readInt32()); |
|
5339 call.isVoicePrivacy = Boolean(Buf.readInt32()); |
|
5340 if (RILQUIRKS_CALLSTATE_EXTRA_UINT32) { |
|
5341 Buf.readInt32(); |
|
5342 } |
|
5343 call.number = Buf.readString(); //TODO munge with TOA |
|
5344 call.numberPresentation = Buf.readInt32(); // CALL_PRESENTATION_* |
|
5345 call.name = Buf.readString(); |
|
5346 call.namePresentation = Buf.readInt32(); |
|
5347 |
|
5348 call.uusInfo = null; |
|
5349 let uusInfoPresent = Buf.readInt32(); |
|
5350 if (uusInfoPresent == 1) { |
|
5351 call.uusInfo = { |
|
5352 type: Buf.readInt32(), |
|
5353 dcs: Buf.readInt32(), |
|
5354 userData: null //XXX TODO byte array?!? |
|
5355 }; |
|
5356 } |
|
5357 |
|
5358 calls[call.callIndex] = call; |
|
5359 } |
|
5360 this._processCalls(calls); |
|
5361 }; |
|
5362 RilObject.prototype[REQUEST_DIAL] = function REQUEST_DIAL(length, options) { |
|
5363 // We already return a successful response before. Don't respond it again! |
|
5364 if (options.rilRequestError) { |
|
5365 this.getFailCauseCode((function(failCause) { |
|
5366 this._removePendingOutgoingCall(failCause); |
|
5367 }).bind(this)); |
|
5368 } |
|
5369 }; |
|
5370 RilObject.prototype[REQUEST_DIAL_EMERGENCY_CALL] = RilObject.prototype[REQUEST_DIAL]; |
|
5371 RilObject.prototype[REQUEST_GET_IMSI] = function REQUEST_GET_IMSI(length, options) { |
|
5372 if (options.rilRequestError) { |
|
5373 return; |
|
5374 } |
|
5375 |
|
5376 this.iccInfoPrivate.imsi = this.context.Buf.readString(); |
|
5377 if (DEBUG) { |
|
5378 this.context.debug("IMSI: " + this.iccInfoPrivate.imsi); |
|
5379 } |
|
5380 |
|
5381 options.rilMessageType = "iccimsi"; |
|
5382 options.imsi = this.iccInfoPrivate.imsi; |
|
5383 this.sendChromeMessage(options); |
|
5384 }; |
|
5385 RilObject.prototype[REQUEST_HANGUP] = function REQUEST_HANGUP(length, options) { |
|
5386 if (options.rilRequestError) { |
|
5387 return; |
|
5388 } |
|
5389 |
|
5390 this.getCurrentCalls(); |
|
5391 }; |
|
5392 RilObject.prototype[REQUEST_HANGUP_WAITING_OR_BACKGROUND] = function REQUEST_HANGUP_WAITING_OR_BACKGROUND(length, options) { |
|
5393 if (options.rilRequestError) { |
|
5394 return; |
|
5395 } |
|
5396 |
|
5397 this.getCurrentCalls(); |
|
5398 }; |
|
5399 RilObject.prototype[REQUEST_HANGUP_FOREGROUND_RESUME_BACKGROUND] = function REQUEST_HANGUP_FOREGROUND_RESUME_BACKGROUND(length, options) { |
|
5400 if (options.rilRequestError) { |
|
5401 return; |
|
5402 } |
|
5403 |
|
5404 this.getCurrentCalls(); |
|
5405 }; |
|
5406 RilObject.prototype[REQUEST_SWITCH_WAITING_OR_HOLDING_AND_ACTIVE] = function REQUEST_SWITCH_WAITING_OR_HOLDING_AND_ACTIVE(length, options) { |
|
5407 options.success = (options.rilRequestError === 0); |
|
5408 if (!options.success) { |
|
5409 options.errorMsg = RIL_ERROR_TO_GECKO_ERROR[options.rilRequestError]; |
|
5410 this.sendChromeMessage(options); |
|
5411 return; |
|
5412 } |
|
5413 |
|
5414 this.sendChromeMessage(options); |
|
5415 this.getCurrentCalls(); |
|
5416 }; |
|
5417 RilObject.prototype[REQUEST_CONFERENCE] = function REQUEST_CONFERENCE(length, options) { |
|
5418 options.success = (options.rilRequestError === 0); |
|
5419 if (!options.success) { |
|
5420 this._hasConferenceRequest = false; |
|
5421 options.errorName = "addError"; |
|
5422 options.errorMsg = RIL_ERROR_TO_GECKO_ERROR[options.rilRequestError]; |
|
5423 this.sendChromeMessage(options); |
|
5424 return; |
|
5425 } |
|
5426 |
|
5427 this.sendChromeMessage(options); |
|
5428 }; |
|
5429 RilObject.prototype[REQUEST_UDUB] = null; |
|
5430 RilObject.prototype[REQUEST_LAST_CALL_FAIL_CAUSE] = function REQUEST_LAST_CALL_FAIL_CAUSE(length, options) { |
|
5431 let Buf = this.context.Buf; |
|
5432 let num = length ? Buf.readInt32() : 0; |
|
5433 let failCause = num ? RIL_CALL_FAILCAUSE_TO_GECKO_CALL_ERROR[Buf.readInt32()] : null; |
|
5434 if (options.callback) { |
|
5435 options.callback(failCause); |
|
5436 } |
|
5437 }; |
|
5438 RilObject.prototype[REQUEST_SIGNAL_STRENGTH] = function REQUEST_SIGNAL_STRENGTH(length, options) { |
|
5439 this._receivedNetworkInfo(NETWORK_INFO_SIGNAL); |
|
5440 |
|
5441 if (options.rilRequestError) { |
|
5442 return; |
|
5443 } |
|
5444 |
|
5445 let Buf = this.context.Buf; |
|
5446 let signal = { |
|
5447 gsmSignalStrength: Buf.readInt32(), |
|
5448 gsmBitErrorRate: Buf.readInt32(), |
|
5449 cdmaDBM: Buf.readInt32(), |
|
5450 cdmaECIO: Buf.readInt32(), |
|
5451 evdoDBM: Buf.readInt32(), |
|
5452 evdoECIO: Buf.readInt32(), |
|
5453 evdoSNR: Buf.readInt32() |
|
5454 }; |
|
5455 |
|
5456 if (!this.v5Legacy) { |
|
5457 signal.lteSignalStrength = Buf.readInt32(); |
|
5458 signal.lteRSRP = Buf.readInt32(); |
|
5459 signal.lteRSRQ = Buf.readInt32(); |
|
5460 signal.lteRSSNR = Buf.readInt32(); |
|
5461 signal.lteCQI = Buf.readInt32(); |
|
5462 } |
|
5463 |
|
5464 if (DEBUG) this.context.debug("signal strength: " + JSON.stringify(signal)); |
|
5465 |
|
5466 this._processSignalStrength(signal); |
|
5467 }; |
|
5468 RilObject.prototype[REQUEST_VOICE_REGISTRATION_STATE] = function REQUEST_VOICE_REGISTRATION_STATE(length, options) { |
|
5469 this._receivedNetworkInfo(NETWORK_INFO_VOICE_REGISTRATION_STATE); |
|
5470 |
|
5471 if (options.rilRequestError) { |
|
5472 return; |
|
5473 } |
|
5474 |
|
5475 let state = this.context.Buf.readStringList(); |
|
5476 if (DEBUG) this.context.debug("voice registration state: " + state); |
|
5477 |
|
5478 this._processVoiceRegistrationState(state); |
|
5479 |
|
5480 if (this.cachedDialRequest && |
|
5481 (this.voiceRegistrationState.emergencyCallsOnly || |
|
5482 this.voiceRegistrationState.connected) && |
|
5483 this.voiceRegistrationState.radioTech != NETWORK_CREG_TECH_UNKNOWN) { |
|
5484 // Radio is ready for making the cached emergency call. |
|
5485 this.cachedDialRequest.callback(); |
|
5486 this.cachedDialRequest = null; |
|
5487 } |
|
5488 }; |
|
5489 RilObject.prototype[REQUEST_DATA_REGISTRATION_STATE] = function REQUEST_DATA_REGISTRATION_STATE(length, options) { |
|
5490 this._receivedNetworkInfo(NETWORK_INFO_DATA_REGISTRATION_STATE); |
|
5491 |
|
5492 if (options.rilRequestError) { |
|
5493 return; |
|
5494 } |
|
5495 |
|
5496 let state = this.context.Buf.readStringList(); |
|
5497 this._processDataRegistrationState(state); |
|
5498 }; |
|
5499 RilObject.prototype[REQUEST_OPERATOR] = function REQUEST_OPERATOR(length, options) { |
|
5500 this._receivedNetworkInfo(NETWORK_INFO_OPERATOR); |
|
5501 |
|
5502 if (options.rilRequestError) { |
|
5503 return; |
|
5504 } |
|
5505 |
|
5506 let operatorData = this.context.Buf.readStringList(); |
|
5507 if (DEBUG) this.context.debug("Operator: " + operatorData); |
|
5508 this._processOperator(operatorData); |
|
5509 }; |
|
5510 RilObject.prototype[REQUEST_RADIO_POWER] = function REQUEST_RADIO_POWER(length, options) { |
|
5511 if (options.rilMessageType == null) { |
|
5512 // The request was made by ril_worker itself. |
|
5513 if (options.rilRequestError) { |
|
5514 if (this.cachedDialRequest && options.enabled) { |
|
5515 // Turning on radio fails. Notify the error of making an emergency call. |
|
5516 this.cachedDialRequest.onerror(GECKO_ERROR_RADIO_NOT_AVAILABLE); |
|
5517 this.cachedDialRequest = null; |
|
5518 } |
|
5519 } |
|
5520 return; |
|
5521 } |
|
5522 |
|
5523 options.errorMsg = RIL_ERROR_TO_GECKO_ERROR[options.rilRequestError]; |
|
5524 this.sendChromeMessage(options); |
|
5525 }; |
|
5526 RilObject.prototype[REQUEST_DTMF] = null; |
|
5527 RilObject.prototype[REQUEST_SEND_SMS] = function REQUEST_SEND_SMS(length, options) { |
|
5528 this._processSmsSendResult(length, options); |
|
5529 }; |
|
5530 RilObject.prototype[REQUEST_SEND_SMS_EXPECT_MORE] = null; |
|
5531 |
|
5532 RilObject.prototype.readSetupDataCall_v5 = function readSetupDataCall_v5(options) { |
|
5533 if (!options) { |
|
5534 options = {}; |
|
5535 } |
|
5536 let [cid, ifname, addresses, dnses, gateways] = this.context.Buf.readStringList(); |
|
5537 options.cid = cid; |
|
5538 options.ifname = ifname; |
|
5539 options.addresses = addresses ? [addresses] : []; |
|
5540 options.dnses = dnses ? [dnses] : []; |
|
5541 options.gateways = gateways ? [gateways] : []; |
|
5542 options.active = DATACALL_ACTIVE_UNKNOWN; |
|
5543 options.state = GECKO_NETWORK_STATE_CONNECTING; |
|
5544 return options; |
|
5545 }; |
|
5546 |
|
5547 RilObject.prototype[REQUEST_SETUP_DATA_CALL] = function REQUEST_SETUP_DATA_CALL(length, options) { |
|
5548 if (options.rilRequestError) { |
|
5549 // On Data Call generic errors, we shall notify caller |
|
5550 this._sendDataCallError(options, options.rilRequestError); |
|
5551 return; |
|
5552 } |
|
5553 |
|
5554 if (this.v5Legacy) { |
|
5555 // Populate the `options` object with the data call information. That way |
|
5556 // we retain the APN and other info about how the data call was set up. |
|
5557 this.readSetupDataCall_v5(options); |
|
5558 this.currentDataCalls[options.cid] = options; |
|
5559 options.rilMessageType = "datacallstatechange"; |
|
5560 this.sendChromeMessage(options); |
|
5561 // Let's get the list of data calls to ensure we know whether it's active |
|
5562 // or not. |
|
5563 this.getDataCallList(); |
|
5564 return; |
|
5565 } |
|
5566 // Pass `options` along. That way we retain the APN and other info about |
|
5567 // how the data call was set up. |
|
5568 this[REQUEST_DATA_CALL_LIST](length, options); |
|
5569 }; |
|
5570 RilObject.prototype[REQUEST_SIM_IO] = function REQUEST_SIM_IO(length, options) { |
|
5571 let ICCIOHelper = this.context.ICCIOHelper; |
|
5572 if (!length) { |
|
5573 ICCIOHelper.processICCIOError(options); |
|
5574 return; |
|
5575 } |
|
5576 |
|
5577 // Don't need to read rilRequestError since we can know error status from |
|
5578 // sw1 and sw2. |
|
5579 let Buf = this.context.Buf; |
|
5580 options.sw1 = Buf.readInt32(); |
|
5581 options.sw2 = Buf.readInt32(); |
|
5582 if (options.sw1 != ICC_STATUS_NORMAL_ENDING) { |
|
5583 ICCIOHelper.processICCIOError(options); |
|
5584 return; |
|
5585 } |
|
5586 ICCIOHelper.processICCIO(options); |
|
5587 }; |
|
5588 RilObject.prototype[REQUEST_SEND_USSD] = function REQUEST_SEND_USSD(length, options) { |
|
5589 if (DEBUG) { |
|
5590 this.context.debug("REQUEST_SEND_USSD " + JSON.stringify(options)); |
|
5591 } |
|
5592 options.success = (this._ussdSession = options.rilRequestError === 0); |
|
5593 options.errorMsg = RIL_ERROR_TO_GECKO_ERROR[options.rilRequestError]; |
|
5594 this.sendChromeMessage(options); |
|
5595 }; |
|
5596 RilObject.prototype[REQUEST_CANCEL_USSD] = function REQUEST_CANCEL_USSD(length, options) { |
|
5597 if (DEBUG) { |
|
5598 this.context.debug("REQUEST_CANCEL_USSD" + JSON.stringify(options)); |
|
5599 } |
|
5600 options.success = (options.rilRequestError === 0); |
|
5601 this._ussdSession = !options.success; |
|
5602 options.errorMsg = RIL_ERROR_TO_GECKO_ERROR[options.rilRequestError]; |
|
5603 this.sendChromeMessage(options); |
|
5604 }; |
|
5605 RilObject.prototype[REQUEST_GET_CLIR] = function REQUEST_GET_CLIR(length, options) { |
|
5606 options.success = (options.rilRequestError === 0); |
|
5607 if (!options.success) { |
|
5608 options.errorMsg = RIL_ERROR_TO_GECKO_ERROR[options.rilRequestError]; |
|
5609 this.sendChromeMessage(options); |
|
5610 return; |
|
5611 } |
|
5612 |
|
5613 let Buf = this.context.Buf; |
|
5614 let bufLength = Buf.readInt32(); |
|
5615 if (!bufLength || bufLength < 2) { |
|
5616 options.success = false; |
|
5617 options.errorMsg = GECKO_ERROR_GENERIC_FAILURE; |
|
5618 this.sendChromeMessage(options); |
|
5619 return; |
|
5620 } |
|
5621 |
|
5622 options.n = Buf.readInt32(); // Will be TS 27.007 +CLIR parameter 'n'. |
|
5623 options.m = Buf.readInt32(); // Will be TS 27.007 +CLIR parameter 'm'. |
|
5624 |
|
5625 if (options.rilMessageType === "sendMMI") { |
|
5626 // TS 27.007 +CLIR parameter 'm'. |
|
5627 switch (options.m) { |
|
5628 // CLIR not provisioned. |
|
5629 case 0: |
|
5630 options.statusMessage = MMI_SM_KS_SERVICE_NOT_PROVISIONED; |
|
5631 break; |
|
5632 // CLIR provisioned in permanent mode. |
|
5633 case 1: |
|
5634 options.statusMessage = MMI_SM_KS_CLIR_PERMANENT; |
|
5635 break; |
|
5636 // Unknown (e.g. no network, etc.). |
|
5637 case 2: |
|
5638 options.success = false; |
|
5639 options.errorMsg = MMI_ERROR_KS_ERROR; |
|
5640 break; |
|
5641 // CLIR temporary mode presentation restricted. |
|
5642 case 3: |
|
5643 // TS 27.007 +CLIR parameter 'n'. |
|
5644 switch (options.n) { |
|
5645 // Default. |
|
5646 case 0: |
|
5647 // CLIR invocation. |
|
5648 case 1: |
|
5649 options.statusMessage = MMI_SM_KS_CLIR_DEFAULT_ON_NEXT_CALL_ON; |
|
5650 break; |
|
5651 // CLIR suppression. |
|
5652 case 2: |
|
5653 options.statusMessage = MMI_SM_KS_CLIR_DEFAULT_ON_NEXT_CALL_OFF; |
|
5654 break; |
|
5655 default: |
|
5656 options.success = false; |
|
5657 options.errorMsg = GECKO_ERROR_GENERIC_FAILURE; |
|
5658 break; |
|
5659 } |
|
5660 break; |
|
5661 // CLIR temporary mode presentation allowed. |
|
5662 case 4: |
|
5663 // TS 27.007 +CLIR parameter 'n'. |
|
5664 switch (options.n) { |
|
5665 // Default. |
|
5666 case 0: |
|
5667 // CLIR suppression. |
|
5668 case 2: |
|
5669 options.statusMessage = MMI_SM_KS_CLIR_DEFAULT_OFF_NEXT_CALL_OFF; |
|
5670 break; |
|
5671 // CLIR invocation. |
|
5672 case 1: |
|
5673 options.statusMessage = MMI_SM_KS_CLIR_DEFAULT_OFF_NEXT_CALL_ON; |
|
5674 break; |
|
5675 default: |
|
5676 options.success = false; |
|
5677 options.errorMsg = GECKO_ERROR_GENERIC_FAILURE; |
|
5678 break; |
|
5679 } |
|
5680 break; |
|
5681 default: |
|
5682 options.success = false; |
|
5683 options.errorMsg = GECKO_ERROR_GENERIC_FAILURE; |
|
5684 break; |
|
5685 } |
|
5686 } |
|
5687 |
|
5688 this.sendChromeMessage(options); |
|
5689 }; |
|
5690 RilObject.prototype[REQUEST_SET_CLIR] = function REQUEST_SET_CLIR(length, options) { |
|
5691 if (options.rilMessageType == null) { |
|
5692 // The request was made by ril_worker itself automatically. Don't report. |
|
5693 return; |
|
5694 } |
|
5695 options.success = (options.rilRequestError === 0); |
|
5696 if (!options.success) { |
|
5697 options.errorMsg = RIL_ERROR_TO_GECKO_ERROR[options.rilRequestError]; |
|
5698 } else if (options.rilMessageType === "sendMMI") { |
|
5699 switch (options.procedure) { |
|
5700 case MMI_PROCEDURE_ACTIVATION: |
|
5701 options.statusMessage = MMI_SM_KS_SERVICE_ENABLED; |
|
5702 break; |
|
5703 case MMI_PROCEDURE_DEACTIVATION: |
|
5704 options.statusMessage = MMI_SM_KS_SERVICE_DISABLED; |
|
5705 break; |
|
5706 } |
|
5707 } |
|
5708 this.sendChromeMessage(options); |
|
5709 }; |
|
5710 |
|
5711 RilObject.prototype[REQUEST_QUERY_CALL_FORWARD_STATUS] = |
|
5712 function REQUEST_QUERY_CALL_FORWARD_STATUS(length, options) { |
|
5713 options.success = (options.rilRequestError === 0); |
|
5714 if (!options.success) { |
|
5715 options.errorMsg = RIL_ERROR_TO_GECKO_ERROR[options.rilRequestError]; |
|
5716 this.sendChromeMessage(options); |
|
5717 return; |
|
5718 } |
|
5719 |
|
5720 let Buf = this.context.Buf; |
|
5721 let rulesLength = 0; |
|
5722 if (length) { |
|
5723 rulesLength = Buf.readInt32(); |
|
5724 } |
|
5725 if (!rulesLength) { |
|
5726 options.success = false; |
|
5727 options.errorMsg = GECKO_ERROR_GENERIC_FAILURE; |
|
5728 this.sendChromeMessage(options); |
|
5729 return; |
|
5730 } |
|
5731 let rules = new Array(rulesLength); |
|
5732 for (let i = 0; i < rulesLength; i++) { |
|
5733 let rule = {}; |
|
5734 rule.active = Buf.readInt32() == 1; // CALL_FORWARD_STATUS_* |
|
5735 rule.reason = Buf.readInt32(); // CALL_FORWARD_REASON_* |
|
5736 rule.serviceClass = Buf.readInt32(); |
|
5737 rule.toa = Buf.readInt32(); |
|
5738 rule.number = Buf.readString(); |
|
5739 rule.timeSeconds = Buf.readInt32(); |
|
5740 rules[i] = rule; |
|
5741 } |
|
5742 options.rules = rules; |
|
5743 if (options.rilMessageType === "sendMMI") { |
|
5744 options.statusMessage = MMI_SM_KS_SERVICE_INTERROGATED; |
|
5745 // MMI query call forwarding options request returns a set of rules that |
|
5746 // will be exposed in the form of an array of nsIDOMMozMobileCFInfo |
|
5747 // instances. |
|
5748 options.additionalInformation = rules; |
|
5749 } |
|
5750 this.sendChromeMessage(options); |
|
5751 }; |
|
5752 RilObject.prototype[REQUEST_SET_CALL_FORWARD] = |
|
5753 function REQUEST_SET_CALL_FORWARD(length, options) { |
|
5754 options.success = (options.rilRequestError === 0); |
|
5755 if (!options.success) { |
|
5756 options.errorMsg = RIL_ERROR_TO_GECKO_ERROR[options.rilRequestError]; |
|
5757 } else if (options.rilMessageType === "sendMMI") { |
|
5758 switch (options.action) { |
|
5759 case CALL_FORWARD_ACTION_ENABLE: |
|
5760 options.statusMessage = MMI_SM_KS_SERVICE_ENABLED; |
|
5761 break; |
|
5762 case CALL_FORWARD_ACTION_DISABLE: |
|
5763 options.statusMessage = MMI_SM_KS_SERVICE_DISABLED; |
|
5764 break; |
|
5765 case CALL_FORWARD_ACTION_REGISTRATION: |
|
5766 options.statusMessage = MMI_SM_KS_SERVICE_REGISTERED; |
|
5767 break; |
|
5768 case CALL_FORWARD_ACTION_ERASURE: |
|
5769 options.statusMessage = MMI_SM_KS_SERVICE_ERASED; |
|
5770 break; |
|
5771 } |
|
5772 } |
|
5773 this.sendChromeMessage(options); |
|
5774 }; |
|
5775 RilObject.prototype[REQUEST_QUERY_CALL_WAITING] = |
|
5776 function REQUEST_QUERY_CALL_WAITING(length, options) { |
|
5777 options.success = (options.rilRequestError === 0); |
|
5778 if (!options.success) { |
|
5779 options.errorMsg = RIL_ERROR_TO_GECKO_ERROR[options.rilRequestError]; |
|
5780 this.sendChromeMessage(options); |
|
5781 return; |
|
5782 } |
|
5783 |
|
5784 if (options.callback) { |
|
5785 options.callback.call(this, options); |
|
5786 return; |
|
5787 } |
|
5788 |
|
5789 let Buf = this.context.Buf; |
|
5790 options.length = Buf.readInt32(); |
|
5791 options.enabled = ((Buf.readInt32() == 1) && |
|
5792 ((Buf.readInt32() & ICC_SERVICE_CLASS_VOICE) == 0x01)); |
|
5793 this.sendChromeMessage(options); |
|
5794 }; |
|
5795 |
|
5796 RilObject.prototype[REQUEST_SET_CALL_WAITING] = function REQUEST_SET_CALL_WAITING(length, options) { |
|
5797 options.success = (options.rilRequestError === 0); |
|
5798 if (!options.success) { |
|
5799 options.errorMsg = RIL_ERROR_TO_GECKO_ERROR[options.rilRequestError]; |
|
5800 this.sendChromeMessage(options); |
|
5801 return; |
|
5802 } |
|
5803 |
|
5804 if (options.callback) { |
|
5805 options.callback.call(this, options); |
|
5806 return; |
|
5807 } |
|
5808 |
|
5809 this.sendChromeMessage(options); |
|
5810 }; |
|
5811 RilObject.prototype[REQUEST_SMS_ACKNOWLEDGE] = null; |
|
5812 RilObject.prototype[REQUEST_GET_IMEI] = function REQUEST_GET_IMEI(length, options) { |
|
5813 this.IMEI = this.context.Buf.readString(); |
|
5814 let rilMessageType = options.rilMessageType; |
|
5815 // So far we only send the IMEI back to chrome if it was requested via MMI. |
|
5816 if (rilMessageType !== "sendMMI") { |
|
5817 return; |
|
5818 } |
|
5819 |
|
5820 options.success = (options.rilRequestError === 0); |
|
5821 options.errorMsg = RIL_ERROR_TO_GECKO_ERROR[options.rilRequestError]; |
|
5822 if ((!options.success || this.IMEI == null) && !options.errorMsg) { |
|
5823 options.errorMsg = GECKO_ERROR_GENERIC_FAILURE; |
|
5824 } |
|
5825 options.statusMessage = this.IMEI; |
|
5826 this.sendChromeMessage(options); |
|
5827 }; |
|
5828 RilObject.prototype[REQUEST_GET_IMEISV] = function REQUEST_GET_IMEISV(length, options) { |
|
5829 if (options.rilRequestError) { |
|
5830 return; |
|
5831 } |
|
5832 |
|
5833 this.IMEISV = this.context.Buf.readString(); |
|
5834 }; |
|
5835 RilObject.prototype[REQUEST_ANSWER] = null; |
|
5836 RilObject.prototype[REQUEST_DEACTIVATE_DATA_CALL] = function REQUEST_DEACTIVATE_DATA_CALL(length, options) { |
|
5837 if (options.rilRequestError) { |
|
5838 return; |
|
5839 } |
|
5840 |
|
5841 let datacall = this.currentDataCalls[options.cid]; |
|
5842 delete this.currentDataCalls[options.cid]; |
|
5843 datacall.state = GECKO_NETWORK_STATE_UNKNOWN; |
|
5844 datacall.rilMessageType = "datacallstatechange"; |
|
5845 this.sendChromeMessage(datacall); |
|
5846 }; |
|
5847 RilObject.prototype[REQUEST_QUERY_FACILITY_LOCK] = function REQUEST_QUERY_FACILITY_LOCK(length, options) { |
|
5848 options.success = (options.rilRequestError === 0); |
|
5849 if (!options.success) { |
|
5850 options.errorMsg = RIL_ERROR_TO_GECKO_ERROR[options.rilRequestError]; |
|
5851 } |
|
5852 |
|
5853 let services; |
|
5854 if (length) { |
|
5855 // Buf.readInt32List()[0] for Call Barring is a bit vector of services. |
|
5856 services = this.context.Buf.readInt32List()[0]; |
|
5857 } else { |
|
5858 options.success = false; |
|
5859 options.errorMsg = GECKO_ERROR_GENERIC_FAILURE; |
|
5860 this.sendChromeMessage(options); |
|
5861 return; |
|
5862 } |
|
5863 |
|
5864 options.enabled = services === 0 ? false : true; |
|
5865 |
|
5866 if (options.success && (options.rilMessageType === "sendMMI")) { |
|
5867 if (!options.enabled) { |
|
5868 options.statusMessage = MMI_SM_KS_SERVICE_DISABLED; |
|
5869 } else { |
|
5870 options.statusMessage = MMI_SM_KS_SERVICE_ENABLED_FOR; |
|
5871 let serviceClass = []; |
|
5872 for (let serviceClassMask = 1; |
|
5873 serviceClassMask <= ICC_SERVICE_CLASS_MAX; |
|
5874 serviceClassMask <<= 1) { |
|
5875 if ((serviceClassMask & services) !== 0) { |
|
5876 serviceClass.push(MMI_KS_SERVICE_CLASS_MAPPING[serviceClassMask]); |
|
5877 } |
|
5878 } |
|
5879 |
|
5880 options.additionalInformation = serviceClass; |
|
5881 } |
|
5882 } |
|
5883 this.sendChromeMessage(options); |
|
5884 }; |
|
5885 RilObject.prototype[REQUEST_SET_FACILITY_LOCK] = function REQUEST_SET_FACILITY_LOCK(length, options) { |
|
5886 options.success = (options.rilRequestError === 0); |
|
5887 if (!options.success) { |
|
5888 options.errorMsg = RIL_ERROR_TO_GECKO_ERROR[options.rilRequestError]; |
|
5889 } |
|
5890 |
|
5891 options.retryCount = length ? this.context.Buf.readInt32List()[0] : -1; |
|
5892 |
|
5893 if (options.success && (options.rilMessageType === "sendMMI")) { |
|
5894 switch (options.procedure) { |
|
5895 case MMI_PROCEDURE_ACTIVATION: |
|
5896 options.statusMessage = MMI_SM_KS_SERVICE_ENABLED; |
|
5897 break; |
|
5898 case MMI_PROCEDURE_DEACTIVATION: |
|
5899 options.statusMessage = MMI_SM_KS_SERVICE_DISABLED; |
|
5900 break; |
|
5901 } |
|
5902 } |
|
5903 this.sendChromeMessage(options); |
|
5904 }; |
|
5905 RilObject.prototype[REQUEST_CHANGE_BARRING_PASSWORD] = |
|
5906 function REQUEST_CHANGE_BARRING_PASSWORD(length, options) { |
|
5907 if (options.rilRequestError) { |
|
5908 options.errorMsg = RIL_ERROR_TO_GECKO_ERROR[options.rilRequestError]; |
|
5909 } |
|
5910 this.sendChromeMessage(options); |
|
5911 }; |
|
5912 RilObject.prototype[REQUEST_SIM_OPEN_CHANNEL] = function REQUEST_SIM_OPEN_CHANNEL(length, options) { |
|
5913 if (options.rilRequestError) { |
|
5914 options.errorMsg = RIL_ERROR_TO_GECKO_ERROR[options.rilRequestError]; |
|
5915 this.sendChromeMessage(options); |
|
5916 return; |
|
5917 } |
|
5918 |
|
5919 options.channel = this.context.Buf.readInt32(); |
|
5920 if (DEBUG) { |
|
5921 this.context.debug("Setting channel number in options: " + options.channel); |
|
5922 } |
|
5923 this.sendChromeMessage(options); |
|
5924 }; |
|
5925 RilObject.prototype[REQUEST_SIM_CLOSE_CHANNEL] = function REQUEST_SIM_CLOSE_CHANNEL(length, options) { |
|
5926 if (options.rilRequestError) { |
|
5927 options.error = RIL_ERROR_TO_GECKO_ERROR[options.rilRequestError]; |
|
5928 this.sendChromeMessage(options); |
|
5929 return; |
|
5930 } |
|
5931 |
|
5932 // No return value |
|
5933 this.sendChromeMessage(options); |
|
5934 }; |
|
5935 RilObject.prototype[REQUEST_SIM_ACCESS_CHANNEL] = function REQUEST_SIM_ACCESS_CHANNEL(length, options) { |
|
5936 if (options.rilRequestError) { |
|
5937 options.error = RIL_ERROR_TO_GECKO_ERROR[options.rilRequestError]; |
|
5938 this.sendChromeMessage(options); |
|
5939 } |
|
5940 |
|
5941 let Buf = this.context.Buf; |
|
5942 options.sw1 = Buf.readInt32(); |
|
5943 options.sw2 = Buf.readInt32(); |
|
5944 options.simResponse = Buf.readString(); |
|
5945 if (DEBUG) { |
|
5946 this.context.debug("Setting return values for RIL[REQUEST_SIM_ACCESS_CHANNEL]: [" + |
|
5947 options.sw1 + "," + |
|
5948 options.sw2 + ", " + |
|
5949 options.simResponse + "]"); |
|
5950 } |
|
5951 this.sendChromeMessage(options); |
|
5952 }; |
|
5953 RilObject.prototype[REQUEST_QUERY_NETWORK_SELECTION_MODE] = function REQUEST_QUERY_NETWORK_SELECTION_MODE(length, options) { |
|
5954 this._receivedNetworkInfo(NETWORK_INFO_NETWORK_SELECTION_MODE); |
|
5955 |
|
5956 if (options.rilRequestError) { |
|
5957 return; |
|
5958 } |
|
5959 |
|
5960 let mode = this.context.Buf.readInt32List(); |
|
5961 let selectionMode; |
|
5962 |
|
5963 switch (mode[0]) { |
|
5964 case NETWORK_SELECTION_MODE_AUTOMATIC: |
|
5965 selectionMode = GECKO_NETWORK_SELECTION_AUTOMATIC; |
|
5966 break; |
|
5967 case NETWORK_SELECTION_MODE_MANUAL: |
|
5968 selectionMode = GECKO_NETWORK_SELECTION_MANUAL; |
|
5969 break; |
|
5970 default: |
|
5971 selectionMode = GECKO_NETWORK_SELECTION_UNKNOWN; |
|
5972 break; |
|
5973 } |
|
5974 |
|
5975 if (this.networkSelectionMode != selectionMode) { |
|
5976 this.networkSelectionMode = options.mode = selectionMode; |
|
5977 options.rilMessageType = "networkselectionmodechange"; |
|
5978 this._sendNetworkInfoMessage(NETWORK_INFO_NETWORK_SELECTION_MODE, options); |
|
5979 } |
|
5980 }; |
|
5981 RilObject.prototype[REQUEST_SET_NETWORK_SELECTION_AUTOMATIC] = function REQUEST_SET_NETWORK_SELECTION_AUTOMATIC(length, options) { |
|
5982 if (options.rilRequestError) { |
|
5983 options.errorMsg = RIL_ERROR_TO_GECKO_ERROR[options.rilRequestError]; |
|
5984 } |
|
5985 |
|
5986 this.sendChromeMessage(options); |
|
5987 }; |
|
5988 RilObject.prototype[REQUEST_SET_NETWORK_SELECTION_MANUAL] = function REQUEST_SET_NETWORK_SELECTION_MANUAL(length, options) { |
|
5989 if (options.rilRequestError) { |
|
5990 options.errorMsg = RIL_ERROR_TO_GECKO_ERROR[options.rilRequestError]; |
|
5991 } |
|
5992 |
|
5993 this.sendChromeMessage(options); |
|
5994 }; |
|
5995 RilObject.prototype[REQUEST_QUERY_AVAILABLE_NETWORKS] = function REQUEST_QUERY_AVAILABLE_NETWORKS(length, options) { |
|
5996 if (options.rilRequestError) { |
|
5997 options.errorMsg = RIL_ERROR_TO_GECKO_ERROR[options.rilRequestError]; |
|
5998 } else { |
|
5999 options.networks = this._processNetworks(); |
|
6000 } |
|
6001 this.sendChromeMessage(options); |
|
6002 }; |
|
6003 RilObject.prototype[REQUEST_DTMF_START] = null; |
|
6004 RilObject.prototype[REQUEST_DTMF_STOP] = null; |
|
6005 RilObject.prototype[REQUEST_BASEBAND_VERSION] = function REQUEST_BASEBAND_VERSION(length, options) { |
|
6006 if (options.rilRequestError) { |
|
6007 return; |
|
6008 } |
|
6009 |
|
6010 this.basebandVersion = this.context.Buf.readString(); |
|
6011 if (DEBUG) this.context.debug("Baseband version: " + this.basebandVersion); |
|
6012 }; |
|
6013 RilObject.prototype[REQUEST_SEPARATE_CONNECTION] = function REQUEST_SEPARATE_CONNECTION(length, options) { |
|
6014 options.success = (options.rilRequestError === 0); |
|
6015 if (!options.success) { |
|
6016 options.errorName = "removeError"; |
|
6017 options.errorMsg = RIL_ERROR_TO_GECKO_ERROR[options.rilRequestError]; |
|
6018 this.sendChromeMessage(options); |
|
6019 return; |
|
6020 } |
|
6021 |
|
6022 this.sendChromeMessage(options); |
|
6023 }; |
|
6024 RilObject.prototype[REQUEST_SET_MUTE] = null; |
|
6025 RilObject.prototype[REQUEST_GET_MUTE] = null; |
|
6026 RilObject.prototype[REQUEST_QUERY_CLIP] = function REQUEST_QUERY_CLIP(length, options) { |
|
6027 options.success = (options.rilRequestError === 0); |
|
6028 if (!options.success) { |
|
6029 options.errorMsg = RIL_ERROR_TO_GECKO_ERROR[options.rilRequestError]; |
|
6030 this.sendChromeMessage(options); |
|
6031 return; |
|
6032 } |
|
6033 |
|
6034 let Buf = this.context.Buf; |
|
6035 let bufLength = Buf.readInt32(); |
|
6036 if (!bufLength) { |
|
6037 options.success = false; |
|
6038 options.errorMsg = GECKO_ERROR_GENERIC_FAILURE; |
|
6039 this.sendChromeMessage(options); |
|
6040 return; |
|
6041 } |
|
6042 |
|
6043 // options.provisioned informs about the called party receives the calling |
|
6044 // party's address information: |
|
6045 // 0 for CLIP not provisioned |
|
6046 // 1 for CLIP provisioned |
|
6047 // 2 for unknown |
|
6048 options.provisioned = Buf.readInt32(); |
|
6049 if (options.rilMessageType === "sendMMI") { |
|
6050 switch (options.provisioned) { |
|
6051 case 0: |
|
6052 options.statusMessage = MMI_SM_KS_SERVICE_DISABLED; |
|
6053 break; |
|
6054 case 1: |
|
6055 options.statusMessage = MMI_SM_KS_SERVICE_ENABLED; |
|
6056 break; |
|
6057 default: |
|
6058 options.success = false; |
|
6059 options.errorMsg = MMI_ERROR_KS_ERROR; |
|
6060 break; |
|
6061 } |
|
6062 } |
|
6063 this.sendChromeMessage(options); |
|
6064 }; |
|
6065 RilObject.prototype[REQUEST_LAST_DATA_CALL_FAIL_CAUSE] = null; |
|
6066 |
|
6067 /** |
|
6068 * V3: |
|
6069 * # address - A space-delimited list of addresses. |
|
6070 * |
|
6071 * V4: |
|
6072 * # address - An address. |
|
6073 * |
|
6074 * V5: |
|
6075 * # addresses - A space-delimited list of addresses. |
|
6076 * # dnses - A space-delimited list of DNS server addresses. |
|
6077 * |
|
6078 * V6: |
|
6079 * # addresses - A space-delimited list of addresses with optional "/" prefix |
|
6080 * length. |
|
6081 * # dnses - A space-delimited list of DNS server addresses. |
|
6082 * # gateways - A space-delimited list of default gateway addresses. |
|
6083 */ |
|
6084 RilObject.prototype.readDataCall_v5 = function(options) { |
|
6085 if (!options) { |
|
6086 options = {}; |
|
6087 } |
|
6088 let Buf = this.context.Buf; |
|
6089 options.cid = Buf.readInt32().toString(); |
|
6090 options.active = Buf.readInt32(); // DATACALL_ACTIVE_* |
|
6091 options.type = Buf.readString(); |
|
6092 options.apn = Buf.readString(); |
|
6093 let addresses = Buf.readString(); |
|
6094 let dnses = Buf.readString(); |
|
6095 options.addresses = addresses ? addresses.split(" ") : []; |
|
6096 options.dnses = dnses ? dnses.split(" ") : []; |
|
6097 options.gateways = []; |
|
6098 return options; |
|
6099 }; |
|
6100 |
|
6101 RilObject.prototype.readDataCall_v6 = function(options) { |
|
6102 if (!options) { |
|
6103 options = {}; |
|
6104 } |
|
6105 let Buf = this.context.Buf; |
|
6106 options.status = Buf.readInt32(); // DATACALL_FAIL_* |
|
6107 options.suggestedRetryTime = Buf.readInt32(); |
|
6108 options.cid = Buf.readInt32().toString(); |
|
6109 options.active = Buf.readInt32(); // DATACALL_ACTIVE_* |
|
6110 options.type = Buf.readString(); |
|
6111 options.ifname = Buf.readString(); |
|
6112 let addresses = Buf.readString(); |
|
6113 let dnses = Buf.readString(); |
|
6114 let gateways = Buf.readString(); |
|
6115 options.addresses = addresses ? addresses.split(" ") : []; |
|
6116 options.dnses = dnses ? dnses.split(" ") : []; |
|
6117 options.gateways = gateways ? gateways.split(" ") : []; |
|
6118 return options; |
|
6119 }; |
|
6120 |
|
6121 RilObject.prototype[REQUEST_DATA_CALL_LIST] = function REQUEST_DATA_CALL_LIST(length, options) { |
|
6122 if (options.rilRequestError) { |
|
6123 return; |
|
6124 } |
|
6125 |
|
6126 if (!length) { |
|
6127 this._processDataCallList(null); |
|
6128 return; |
|
6129 } |
|
6130 |
|
6131 let Buf = this.context.Buf; |
|
6132 let version = 0; |
|
6133 if (!this.v5Legacy) { |
|
6134 version = Buf.readInt32(); |
|
6135 } |
|
6136 let num = Buf.readInt32(); |
|
6137 let datacalls = {}; |
|
6138 for (let i = 0; i < num; i++) { |
|
6139 let datacall; |
|
6140 if (version < 6) { |
|
6141 datacall = this.readDataCall_v5(); |
|
6142 } else { |
|
6143 datacall = this.readDataCall_v6(); |
|
6144 } |
|
6145 datacalls[datacall.cid] = datacall; |
|
6146 } |
|
6147 |
|
6148 let newDataCallOptions = null; |
|
6149 if (options.rilRequestType == REQUEST_SETUP_DATA_CALL) { |
|
6150 newDataCallOptions = options; |
|
6151 } |
|
6152 this._processDataCallList(datacalls, newDataCallOptions); |
|
6153 }; |
|
6154 RilObject.prototype[REQUEST_RESET_RADIO] = null; |
|
6155 RilObject.prototype[REQUEST_OEM_HOOK_RAW] = null; |
|
6156 RilObject.prototype[REQUEST_OEM_HOOK_STRINGS] = null; |
|
6157 RilObject.prototype[REQUEST_SCREEN_STATE] = null; |
|
6158 RilObject.prototype[REQUEST_SET_SUPP_SVC_NOTIFICATION] = null; |
|
6159 RilObject.prototype[REQUEST_WRITE_SMS_TO_SIM] = function REQUEST_WRITE_SMS_TO_SIM(length, options) { |
|
6160 if (options.rilRequestError) { |
|
6161 // `The MS shall return a "protocol error, unspecified" error message if |
|
6162 // the short message cannot be stored in the (U)SIM, and there is other |
|
6163 // message storage available at the MS` ~ 3GPP TS 23.038 section 4. Here |
|
6164 // we assume we always have indexed db as another storage. |
|
6165 this.acknowledgeGsmSms(false, PDU_FCS_PROTOCOL_ERROR); |
|
6166 } else { |
|
6167 this.acknowledgeGsmSms(true, PDU_FCS_OK); |
|
6168 } |
|
6169 }; |
|
6170 RilObject.prototype[REQUEST_DELETE_SMS_ON_SIM] = null; |
|
6171 RilObject.prototype[REQUEST_SET_BAND_MODE] = null; |
|
6172 RilObject.prototype[REQUEST_QUERY_AVAILABLE_BAND_MODE] = null; |
|
6173 RilObject.prototype[REQUEST_STK_GET_PROFILE] = null; |
|
6174 RilObject.prototype[REQUEST_STK_SET_PROFILE] = null; |
|
6175 RilObject.prototype[REQUEST_STK_SEND_ENVELOPE_COMMAND] = null; |
|
6176 RilObject.prototype[REQUEST_STK_SEND_TERMINAL_RESPONSE] = null; |
|
6177 RilObject.prototype[REQUEST_STK_HANDLE_CALL_SETUP_REQUESTED_FROM_SIM] = null; |
|
6178 RilObject.prototype[REQUEST_EXPLICIT_CALL_TRANSFER] = null; |
|
6179 RilObject.prototype[REQUEST_SET_PREFERRED_NETWORK_TYPE] = function REQUEST_SET_PREFERRED_NETWORK_TYPE(length, options) { |
|
6180 if (options.networkType == null) { |
|
6181 // The request was made by ril_worker itself automatically. Don't report. |
|
6182 return; |
|
6183 } |
|
6184 |
|
6185 if (options.rilRequestError) { |
|
6186 options.success = false; |
|
6187 options.errorMsg = RIL_ERROR_TO_GECKO_ERROR[options.rilRequestError]; |
|
6188 } else { |
|
6189 options.success = true; |
|
6190 } |
|
6191 this.sendChromeMessage(options); |
|
6192 }; |
|
6193 RilObject.prototype[REQUEST_GET_PREFERRED_NETWORK_TYPE] = function REQUEST_GET_PREFERRED_NETWORK_TYPE(length, options) { |
|
6194 if (options.rilRequestError) { |
|
6195 options.success = false; |
|
6196 options.errorMsg = RIL_ERROR_TO_GECKO_ERROR[options.rilRequestError]; |
|
6197 this.sendChromeMessage(options); |
|
6198 return; |
|
6199 } |
|
6200 |
|
6201 let results = this.context.Buf.readInt32List(); |
|
6202 options.networkType = this.preferredNetworkType = results[0]; |
|
6203 options.success = true; |
|
6204 |
|
6205 this.sendChromeMessage(options); |
|
6206 }; |
|
6207 RilObject.prototype[REQUEST_GET_NEIGHBORING_CELL_IDS] = null; |
|
6208 RilObject.prototype[REQUEST_SET_LOCATION_UPDATES] = null; |
|
6209 RilObject.prototype[REQUEST_CDMA_SET_SUBSCRIPTION_SOURCE] = null; |
|
6210 RilObject.prototype[REQUEST_CDMA_SET_ROAMING_PREFERENCE] = function REQUEST_CDMA_SET_ROAMING_PREFERENCE(length, options) { |
|
6211 if (options.rilRequestError) { |
|
6212 options.errorMsg = RIL_ERROR_TO_GECKO_ERROR[options.rilRequestError]; |
|
6213 } |
|
6214 this.sendChromeMessage(options); |
|
6215 }; |
|
6216 RilObject.prototype[REQUEST_CDMA_QUERY_ROAMING_PREFERENCE] = function REQUEST_CDMA_QUERY_ROAMING_PREFERENCE(length, options) { |
|
6217 if (options.rilRequestError) { |
|
6218 options.errorMsg = RIL_ERROR_TO_GECKO_ERROR[options.rilRequestError]; |
|
6219 } else { |
|
6220 let mode = this.context.Buf.readInt32List(); |
|
6221 options.mode = CDMA_ROAMING_PREFERENCE_TO_GECKO[mode[0]]; |
|
6222 } |
|
6223 this.sendChromeMessage(options); |
|
6224 }; |
|
6225 RilObject.prototype[REQUEST_SET_TTY_MODE] = null; |
|
6226 RilObject.prototype[REQUEST_QUERY_TTY_MODE] = null; |
|
6227 RilObject.prototype[REQUEST_CDMA_SET_PREFERRED_VOICE_PRIVACY_MODE] = function REQUEST_CDMA_SET_PREFERRED_VOICE_PRIVACY_MODE(length, options) { |
|
6228 if (options.rilRequestError) { |
|
6229 options.errorMsg = RIL_ERROR_TO_GECKO_ERROR[options.rilRequestError]; |
|
6230 this.sendChromeMessage(options); |
|
6231 return; |
|
6232 } |
|
6233 |
|
6234 this.sendChromeMessage(options); |
|
6235 }; |
|
6236 RilObject.prototype[REQUEST_CDMA_QUERY_PREFERRED_VOICE_PRIVACY_MODE] = function REQUEST_CDMA_QUERY_PREFERRED_VOICE_PRIVACY_MODE(length, options) { |
|
6237 if (options.rilRequestError) { |
|
6238 options.errorMsg = RIL_ERROR_TO_GECKO_ERROR[options.rilRequestError]; |
|
6239 this.sendChromeMessage(options); |
|
6240 return; |
|
6241 } |
|
6242 |
|
6243 let enabled = this.context.Buf.readInt32List(); |
|
6244 options.enabled = enabled[0] ? true : false; |
|
6245 this.sendChromeMessage(options); |
|
6246 }; |
|
6247 RilObject.prototype[REQUEST_CDMA_FLASH] = function REQUEST_CDMA_FLASH(length, options) { |
|
6248 options.success = (options.rilRequestError === 0); |
|
6249 if (!options.success) { |
|
6250 if (options.rilMessageType === "conferenceCall") { |
|
6251 options.errorName = "addError"; |
|
6252 } else if (options.rilMessageType === "separateCall") { |
|
6253 options.errorName = "removeError"; |
|
6254 } |
|
6255 options.errorMsg = RIL_ERROR_TO_GECKO_ERROR[options.rilRequestError]; |
|
6256 } |
|
6257 |
|
6258 this.sendChromeMessage(options); |
|
6259 }; |
|
6260 RilObject.prototype[REQUEST_CDMA_BURST_DTMF] = null; |
|
6261 RilObject.prototype[REQUEST_CDMA_VALIDATE_AND_WRITE_AKEY] = null; |
|
6262 RilObject.prototype[REQUEST_CDMA_SEND_SMS] = function REQUEST_CDMA_SEND_SMS(length, options) { |
|
6263 this._processSmsSendResult(length, options); |
|
6264 }; |
|
6265 RilObject.prototype[REQUEST_CDMA_SMS_ACKNOWLEDGE] = null; |
|
6266 RilObject.prototype[REQUEST_GSM_GET_BROADCAST_SMS_CONFIG] = null; |
|
6267 RilObject.prototype[REQUEST_GSM_SET_BROADCAST_SMS_CONFIG] = function REQUEST_GSM_SET_BROADCAST_SMS_CONFIG(length, options) { |
|
6268 if (options.rilRequestError == ERROR_SUCCESS) { |
|
6269 this.setSmsBroadcastActivation(true); |
|
6270 } |
|
6271 }; |
|
6272 RilObject.prototype[REQUEST_GSM_SMS_BROADCAST_ACTIVATION] = null; |
|
6273 RilObject.prototype[REQUEST_CDMA_GET_BROADCAST_SMS_CONFIG] = null; |
|
6274 RilObject.prototype[REQUEST_CDMA_SET_BROADCAST_SMS_CONFIG] = null; |
|
6275 RilObject.prototype[REQUEST_CDMA_SMS_BROADCAST_ACTIVATION] = null; |
|
6276 RilObject.prototype[REQUEST_CDMA_SUBSCRIPTION] = function REQUEST_CDMA_SUBSCRIPTION(length, options) { |
|
6277 if (options.rilRequestError) { |
|
6278 return; |
|
6279 } |
|
6280 |
|
6281 let result = this.context.Buf.readStringList(); |
|
6282 |
|
6283 this.iccInfo.mdn = result[0]; |
|
6284 // The result[1] is Home SID. (Already be handled in readCDMAHome()) |
|
6285 // The result[2] is Home NID. (Already be handled in readCDMAHome()) |
|
6286 // The result[3] is MIN. |
|
6287 this.iccInfo.prlVersion = parseInt(result[4], 10); |
|
6288 |
|
6289 this.context.ICCUtilsHelper.handleICCInfoChange(); |
|
6290 }; |
|
6291 RilObject.prototype[REQUEST_CDMA_WRITE_SMS_TO_RUIM] = null; |
|
6292 RilObject.prototype[REQUEST_CDMA_DELETE_SMS_ON_RUIM] = null; |
|
6293 RilObject.prototype[REQUEST_DEVICE_IDENTITY] = function REQUEST_DEVICE_IDENTITY(length, options) { |
|
6294 if (options.rilRequestError) { |
|
6295 return; |
|
6296 } |
|
6297 |
|
6298 let result = this.context.Buf.readStringList(); |
|
6299 |
|
6300 // The result[0] is for IMEI. (Already be handled in REQUEST_GET_IMEI) |
|
6301 // The result[1] is for IMEISV. (Already be handled in REQUEST_GET_IMEISV) |
|
6302 // They are both ignored. |
|
6303 this.ESN = result[2]; |
|
6304 this.MEID = result[3]; |
|
6305 }; |
|
6306 RilObject.prototype[REQUEST_EXIT_EMERGENCY_CALLBACK_MODE] = function REQUEST_EXIT_EMERGENCY_CALLBACK_MODE(length, options) { |
|
6307 if (options.internal) { |
|
6308 return; |
|
6309 } |
|
6310 |
|
6311 options.success = (options.rilRequestError === 0); |
|
6312 if (!options.success) { |
|
6313 options.errorMsg = RIL_ERROR_TO_GECKO_ERROR[options.rilRequestError]; |
|
6314 } |
|
6315 this.sendChromeMessage(options); |
|
6316 }; |
|
6317 RilObject.prototype[REQUEST_GET_SMSC_ADDRESS] = function REQUEST_GET_SMSC_ADDRESS(length, options) { |
|
6318 this.SMSC = options.rilRequestError ? null : this.context.Buf.readString(); |
|
6319 |
|
6320 if (!options.rilMessageType || options.rilMessageType !== "getSmscAddress") { |
|
6321 return; |
|
6322 } |
|
6323 |
|
6324 options.smscAddress = this.SMSC; |
|
6325 options.errorMsg = RIL_ERROR_TO_GECKO_ERROR[options.rilRequestError]; |
|
6326 this.sendChromeMessage(options); |
|
6327 }; |
|
6328 RilObject.prototype[REQUEST_SET_SMSC_ADDRESS] = null; |
|
6329 RilObject.prototype[REQUEST_REPORT_SMS_MEMORY_STATUS] = null; |
|
6330 RilObject.prototype[REQUEST_REPORT_STK_SERVICE_IS_RUNNING] = null; |
|
6331 RilObject.prototype[REQUEST_ACKNOWLEDGE_INCOMING_GSM_SMS_WITH_PDU] = null; |
|
6332 RilObject.prototype[REQUEST_STK_SEND_ENVELOPE_WITH_STATUS] = function REQUEST_STK_SEND_ENVELOPE_WITH_STATUS(length, options) { |
|
6333 if (options.rilRequestError) { |
|
6334 this.acknowledgeGsmSms(false, PDU_FCS_UNSPECIFIED); |
|
6335 return; |
|
6336 } |
|
6337 |
|
6338 let Buf = this.context.Buf; |
|
6339 let sw1 = Buf.readInt32(); |
|
6340 let sw2 = Buf.readInt32(); |
|
6341 if ((sw1 == ICC_STATUS_SAT_BUSY) && (sw2 === 0x00)) { |
|
6342 this.acknowledgeGsmSms(false, PDU_FCS_USAT_BUSY); |
|
6343 return; |
|
6344 } |
|
6345 |
|
6346 let success = ((sw1 == ICC_STATUS_NORMAL_ENDING) && (sw2 === 0x00)) |
|
6347 || (sw1 == ICC_STATUS_NORMAL_ENDING_WITH_EXTRA); |
|
6348 |
|
6349 let messageStringLength = Buf.readInt32(); // In semi-octets |
|
6350 let responsePduLen = messageStringLength / 2; // In octets |
|
6351 if (!responsePduLen) { |
|
6352 this.acknowledgeGsmSms(success, success ? PDU_FCS_OK |
|
6353 : PDU_FCS_USIM_DATA_DOWNLOAD_ERROR); |
|
6354 return; |
|
6355 } |
|
6356 |
|
6357 this.acknowledgeIncomingGsmSmsWithPDU(success, responsePduLen, options); |
|
6358 }; |
|
6359 RilObject.prototype[REQUEST_VOICE_RADIO_TECH] = function REQUEST_VOICE_RADIO_TECH(length, options) { |
|
6360 if (options.rilRequestError) { |
|
6361 if (DEBUG) { |
|
6362 this.context.debug("Error when getting voice radio tech: " + |
|
6363 options.rilRequestError); |
|
6364 } |
|
6365 return; |
|
6366 } |
|
6367 let radioTech = this.context.Buf.readInt32List(); |
|
6368 this._processRadioTech(radioTech[0]); |
|
6369 }; |
|
6370 RilObject.prototype[REQUEST_GET_UNLOCK_RETRY_COUNT] = function REQUEST_GET_UNLOCK_RETRY_COUNT(length, options) { |
|
6371 options.success = (options.rilRequestError === 0); |
|
6372 if (!options.success) { |
|
6373 options.errorMsg = RIL_ERROR_TO_GECKO_ERROR[options.rilRequestError]; |
|
6374 } |
|
6375 options.retryCount = length ? this.context.Buf.readInt32List()[0] : -1; |
|
6376 this.sendChromeMessage(options); |
|
6377 }; |
|
6378 RilObject.prototype[RIL_REQUEST_GPRS_ATTACH] = null; |
|
6379 RilObject.prototype[RIL_REQUEST_GPRS_DETACH] = null; |
|
6380 RilObject.prototype[UNSOLICITED_RESPONSE_RADIO_STATE_CHANGED] = function UNSOLICITED_RESPONSE_RADIO_STATE_CHANGED() { |
|
6381 let radioState = this.context.Buf.readInt32(); |
|
6382 let newState; |
|
6383 if (radioState == RADIO_STATE_UNAVAILABLE) { |
|
6384 newState = GECKO_RADIOSTATE_UNAVAILABLE; |
|
6385 } else if (radioState == RADIO_STATE_OFF) { |
|
6386 newState = GECKO_RADIOSTATE_OFF; |
|
6387 } else { |
|
6388 newState = GECKO_RADIOSTATE_READY; |
|
6389 } |
|
6390 |
|
6391 if (DEBUG) { |
|
6392 this.context.debug("Radio state changed from '" + this.radioState + |
|
6393 "' to '" + newState + "'"); |
|
6394 } |
|
6395 if (this.radioState == newState) { |
|
6396 return; |
|
6397 } |
|
6398 |
|
6399 switch (radioState) { |
|
6400 case RADIO_STATE_SIM_READY: |
|
6401 case RADIO_STATE_SIM_NOT_READY: |
|
6402 case RADIO_STATE_SIM_LOCKED_OR_ABSENT: |
|
6403 this._isCdma = false; |
|
6404 this._waitingRadioTech = false; |
|
6405 break; |
|
6406 case RADIO_STATE_RUIM_READY: |
|
6407 case RADIO_STATE_RUIM_NOT_READY: |
|
6408 case RADIO_STATE_RUIM_LOCKED_OR_ABSENT: |
|
6409 case RADIO_STATE_NV_READY: |
|
6410 case RADIO_STATE_NV_NOT_READY: |
|
6411 this._isCdma = true; |
|
6412 this._waitingRadioTech = false; |
|
6413 break; |
|
6414 case RADIO_STATE_ON: // RIL v7 |
|
6415 // This value is defined in RIL v7, we will retrieve radio tech by another |
|
6416 // request. We leave _isCdma untouched, and it will be set once we get the |
|
6417 // radio technology. |
|
6418 this._waitingRadioTech = true; |
|
6419 this.getVoiceRadioTechnology(); |
|
6420 break; |
|
6421 } |
|
6422 |
|
6423 if ((this.radioState == GECKO_RADIOSTATE_UNAVAILABLE || |
|
6424 this.radioState == GECKO_RADIOSTATE_OFF) && |
|
6425 newState == GECKO_RADIOSTATE_READY) { |
|
6426 // The radio became available, let's get its info. |
|
6427 if (!this._waitingRadioTech) { |
|
6428 if (this._isCdma) { |
|
6429 this.getDeviceIdentity(); |
|
6430 } else { |
|
6431 this.getIMEI(); |
|
6432 this.getIMEISV(); |
|
6433 } |
|
6434 } |
|
6435 this.getBasebandVersion(); |
|
6436 this.updateCellBroadcastConfig(); |
|
6437 this.setPreferredNetworkType(); |
|
6438 this.setCLIR(); |
|
6439 if (RILQUIRKS_DATA_REGISTRATION_ON_DEMAND && this._attachDataRegistration) { |
|
6440 this.setDataRegistration({attach: true}); |
|
6441 } |
|
6442 } |
|
6443 |
|
6444 this.radioState = newState; |
|
6445 this.sendChromeMessage({ |
|
6446 rilMessageType: "radiostatechange", |
|
6447 radioState: newState |
|
6448 }); |
|
6449 |
|
6450 // If the radio is up and on, so let's query the card state. |
|
6451 // On older RILs only if the card is actually ready, though. |
|
6452 // If _waitingRadioTech is set, we don't need to get icc status now. |
|
6453 if (radioState == RADIO_STATE_UNAVAILABLE || |
|
6454 radioState == RADIO_STATE_OFF || |
|
6455 this._waitingRadioTech) { |
|
6456 return; |
|
6457 } |
|
6458 this.getICCStatus(); |
|
6459 }; |
|
6460 RilObject.prototype[UNSOLICITED_RESPONSE_CALL_STATE_CHANGED] = function UNSOLICITED_RESPONSE_CALL_STATE_CHANGED() { |
|
6461 this.getCurrentCalls(); |
|
6462 }; |
|
6463 RilObject.prototype[UNSOLICITED_RESPONSE_VOICE_NETWORK_STATE_CHANGED] = function UNSOLICITED_RESPONSE_VOICE_NETWORK_STATE_CHANGED() { |
|
6464 if (DEBUG) { |
|
6465 this.context.debug("Network state changed, re-requesting phone state and " + |
|
6466 "ICC status"); |
|
6467 } |
|
6468 this.getICCStatus(); |
|
6469 this.requestNetworkInfo(); |
|
6470 }; |
|
6471 RilObject.prototype[UNSOLICITED_RESPONSE_NEW_SMS] = function UNSOLICITED_RESPONSE_NEW_SMS(length) { |
|
6472 let [message, result] = this.context.GsmPDUHelper.processReceivedSms(length); |
|
6473 |
|
6474 if (message) { |
|
6475 result = this._processSmsMultipart(message); |
|
6476 } |
|
6477 |
|
6478 if (result == PDU_FCS_RESERVED || result == MOZ_FCS_WAIT_FOR_EXPLICIT_ACK) { |
|
6479 return; |
|
6480 } |
|
6481 |
|
6482 // Not reserved FCS values, send ACK now. |
|
6483 this.acknowledgeGsmSms(result == PDU_FCS_OK, result); |
|
6484 }; |
|
6485 RilObject.prototype[UNSOLICITED_RESPONSE_NEW_SMS_STATUS_REPORT] = function UNSOLICITED_RESPONSE_NEW_SMS_STATUS_REPORT(length) { |
|
6486 let result = this._processSmsStatusReport(length); |
|
6487 this.acknowledgeGsmSms(result == PDU_FCS_OK, result); |
|
6488 }; |
|
6489 RilObject.prototype[UNSOLICITED_RESPONSE_NEW_SMS_ON_SIM] = function UNSOLICITED_RESPONSE_NEW_SMS_ON_SIM(length) { |
|
6490 let recordNumber = this.context.Buf.readInt32List()[0]; |
|
6491 |
|
6492 this.context.SimRecordHelper.readSMS( |
|
6493 recordNumber, |
|
6494 function onsuccess(message) { |
|
6495 if (message && message.simStatus === 3) { //New Unread SMS |
|
6496 this._processSmsMultipart(message); |
|
6497 } |
|
6498 }.bind(this), |
|
6499 function onerror(errorMsg) { |
|
6500 if (DEBUG) { |
|
6501 this.context.debug("Failed to Read NEW SMS on SIM #" + recordNumber + |
|
6502 ", errorMsg: " + errorMsg); |
|
6503 } |
|
6504 }); |
|
6505 }; |
|
6506 RilObject.prototype[UNSOLICITED_ON_USSD] = function UNSOLICITED_ON_USSD() { |
|
6507 let [typeCode, message] = this.context.Buf.readStringList(); |
|
6508 if (DEBUG) { |
|
6509 this.context.debug("On USSD. Type Code: " + typeCode + " Message: " + message); |
|
6510 } |
|
6511 |
|
6512 this._ussdSession = (typeCode != "0" && typeCode != "2"); |
|
6513 |
|
6514 this.sendChromeMessage({rilMessageType: "USSDReceived", |
|
6515 message: message, |
|
6516 sessionEnded: !this._ussdSession}); |
|
6517 }; |
|
6518 RilObject.prototype[UNSOLICITED_NITZ_TIME_RECEIVED] = function UNSOLICITED_NITZ_TIME_RECEIVED() { |
|
6519 let dateString = this.context.Buf.readString(); |
|
6520 |
|
6521 // The data contained in the NITZ message is |
|
6522 // in the form "yy/mm/dd,hh:mm:ss(+/-)tz,dt" |
|
6523 // for example: 12/02/16,03:36:08-20,00,310410 |
|
6524 // See also bug 714352 - Listen for NITZ updates from rild. |
|
6525 |
|
6526 if (DEBUG) this.context.debug("DateTimeZone string " + dateString); |
|
6527 |
|
6528 let now = Date.now(); |
|
6529 |
|
6530 let year = parseInt(dateString.substr(0, 2), 10); |
|
6531 let month = parseInt(dateString.substr(3, 2), 10); |
|
6532 let day = parseInt(dateString.substr(6, 2), 10); |
|
6533 let hours = parseInt(dateString.substr(9, 2), 10); |
|
6534 let minutes = parseInt(dateString.substr(12, 2), 10); |
|
6535 let seconds = parseInt(dateString.substr(15, 2), 10); |
|
6536 // Note that |tz| is in 15-min units. |
|
6537 let tz = parseInt(dateString.substr(17, 3), 10); |
|
6538 // Note that |dst| is in 1-hour units and is already applied in |tz|. |
|
6539 let dst = parseInt(dateString.substr(21, 2), 10); |
|
6540 |
|
6541 let timeInMS = Date.UTC(year + PDU_TIMESTAMP_YEAR_OFFSET, month - 1, day, |
|
6542 hours, minutes, seconds); |
|
6543 |
|
6544 if (isNaN(timeInMS)) { |
|
6545 if (DEBUG) this.context.debug("NITZ failed to convert date"); |
|
6546 return; |
|
6547 } |
|
6548 |
|
6549 this.sendChromeMessage({rilMessageType: "nitzTime", |
|
6550 networkTimeInMS: timeInMS, |
|
6551 networkTimeZoneInMinutes: -(tz * 15), |
|
6552 networkDSTInMinutes: -(dst * 60), |
|
6553 receiveTimeInMS: now}); |
|
6554 }; |
|
6555 |
|
6556 RilObject.prototype[UNSOLICITED_SIGNAL_STRENGTH] = function UNSOLICITED_SIGNAL_STRENGTH(length) { |
|
6557 this[REQUEST_SIGNAL_STRENGTH](length, {rilRequestError: ERROR_SUCCESS}); |
|
6558 }; |
|
6559 RilObject.prototype[UNSOLICITED_DATA_CALL_LIST_CHANGED] = function UNSOLICITED_DATA_CALL_LIST_CHANGED(length) { |
|
6560 if (this.v5Legacy) { |
|
6561 this.getDataCallList(); |
|
6562 return; |
|
6563 } |
|
6564 this[REQUEST_DATA_CALL_LIST](length, {rilRequestError: ERROR_SUCCESS}); |
|
6565 }; |
|
6566 RilObject.prototype[UNSOLICITED_SUPP_SVC_NOTIFICATION] = function UNSOLICITED_SUPP_SVC_NOTIFICATION(length) { |
|
6567 let Buf = this.context.Buf; |
|
6568 let info = {}; |
|
6569 info.notificationType = Buf.readInt32(); |
|
6570 info.code = Buf.readInt32(); |
|
6571 info.index = Buf.readInt32(); |
|
6572 info.type = Buf.readInt32(); |
|
6573 info.number = Buf.readString(); |
|
6574 |
|
6575 this._processSuppSvcNotification(info); |
|
6576 }; |
|
6577 |
|
6578 RilObject.prototype[UNSOLICITED_STK_SESSION_END] = function UNSOLICITED_STK_SESSION_END() { |
|
6579 this.sendChromeMessage({rilMessageType: "stksessionend"}); |
|
6580 }; |
|
6581 RilObject.prototype[UNSOLICITED_STK_PROACTIVE_COMMAND] = function UNSOLICITED_STK_PROACTIVE_COMMAND() { |
|
6582 this.processStkProactiveCommand(); |
|
6583 }; |
|
6584 RilObject.prototype[UNSOLICITED_STK_EVENT_NOTIFY] = function UNSOLICITED_STK_EVENT_NOTIFY() { |
|
6585 this.processStkProactiveCommand(); |
|
6586 }; |
|
6587 RilObject.prototype[UNSOLICITED_STK_CALL_SETUP] = null; |
|
6588 RilObject.prototype[UNSOLICITED_SIM_SMS_STORAGE_FULL] = null; |
|
6589 RilObject.prototype[UNSOLICITED_SIM_REFRESH] = null; |
|
6590 RilObject.prototype[UNSOLICITED_CALL_RING] = function UNSOLICITED_CALL_RING() { |
|
6591 let Buf = this.context.Buf; |
|
6592 let info = {rilMessageType: "callRing"}; |
|
6593 let isCDMA = false; //XXX TODO hard-code this for now |
|
6594 if (isCDMA) { |
|
6595 info.isPresent = Buf.readInt32(); |
|
6596 info.signalType = Buf.readInt32(); |
|
6597 info.alertPitch = Buf.readInt32(); |
|
6598 info.signal = Buf.readInt32(); |
|
6599 } |
|
6600 // At this point we don't know much other than the fact there's an incoming |
|
6601 // call, but that's enough to bring up the Phone app already. We'll know |
|
6602 // details once we get a call state changed notification and can then |
|
6603 // dispatch DOM events etc. |
|
6604 this.sendChromeMessage(info); |
|
6605 }; |
|
6606 RilObject.prototype[UNSOLICITED_RESPONSE_SIM_STATUS_CHANGED] = function UNSOLICITED_RESPONSE_SIM_STATUS_CHANGED() { |
|
6607 this.getICCStatus(); |
|
6608 }; |
|
6609 RilObject.prototype[UNSOLICITED_RESPONSE_CDMA_NEW_SMS] = function UNSOLICITED_RESPONSE_CDMA_NEW_SMS(length) { |
|
6610 let [message, result] = this.context.CdmaPDUHelper.processReceivedSms(length); |
|
6611 |
|
6612 if (message) { |
|
6613 if (message.teleservice === PDU_CDMA_MSG_TELESERIVCIE_ID_WAP) { |
|
6614 result = this._processCdmaSmsWapPush(message); |
|
6615 } else if (message.subMsgType === PDU_CDMA_MSG_TYPE_DELIVER_ACK) { |
|
6616 result = this._processCdmaSmsStatusReport(message); |
|
6617 } else { |
|
6618 result = this._processSmsMultipart(message); |
|
6619 } |
|
6620 } |
|
6621 |
|
6622 if (result == PDU_FCS_RESERVED || result == MOZ_FCS_WAIT_FOR_EXPLICIT_ACK) { |
|
6623 return; |
|
6624 } |
|
6625 |
|
6626 // Not reserved FCS values, send ACK now. |
|
6627 this.acknowledgeCdmaSms(result == PDU_FCS_OK, result); |
|
6628 }; |
|
6629 RilObject.prototype[UNSOLICITED_RESPONSE_NEW_BROADCAST_SMS] = function UNSOLICITED_RESPONSE_NEW_BROADCAST_SMS(length) { |
|
6630 let message; |
|
6631 try { |
|
6632 message = |
|
6633 this.context.GsmPDUHelper.readCbMessage(this.context.Buf.readInt32()); |
|
6634 } catch (e) { |
|
6635 if (DEBUG) { |
|
6636 this.context.debug("Failed to parse Cell Broadcast message: " + |
|
6637 JSON.stringify(e)); |
|
6638 } |
|
6639 return; |
|
6640 } |
|
6641 |
|
6642 message = this._processReceivedSmsCbPage(message); |
|
6643 if (!message) { |
|
6644 return; |
|
6645 } |
|
6646 |
|
6647 message.rilMessageType = "cellbroadcast-received"; |
|
6648 this.sendChromeMessage(message); |
|
6649 }; |
|
6650 RilObject.prototype[UNSOLICITED_CDMA_RUIM_SMS_STORAGE_FULL] = null; |
|
6651 RilObject.prototype[UNSOLICITED_RESTRICTED_STATE_CHANGED] = null; |
|
6652 RilObject.prototype[UNSOLICITED_ENTER_EMERGENCY_CALLBACK_MODE] = function UNSOLICITED_ENTER_EMERGENCY_CALLBACK_MODE() { |
|
6653 this._handleChangedEmergencyCbMode(true); |
|
6654 }; |
|
6655 RilObject.prototype[UNSOLICITED_CDMA_CALL_WAITING] = function UNSOLICITED_CDMA_CALL_WAITING(length) { |
|
6656 let Buf = this.context.Buf; |
|
6657 let call = {}; |
|
6658 call.number = Buf.readString(); |
|
6659 call.numberPresentation = Buf.readInt32(); |
|
6660 call.name = Buf.readString(); |
|
6661 call.namePresentation = Buf.readInt32(); |
|
6662 call.isPresent = Buf.readInt32(); |
|
6663 call.signalType = Buf.readInt32(); |
|
6664 call.alertPitch = Buf.readInt32(); |
|
6665 call.signal = Buf.readInt32(); |
|
6666 this.sendChromeMessage({rilMessageType: "cdmaCallWaiting", |
|
6667 number: call.number}); |
|
6668 }; |
|
6669 RilObject.prototype[UNSOLICITED_CDMA_OTA_PROVISION_STATUS] = function UNSOLICITED_CDMA_OTA_PROVISION_STATUS() { |
|
6670 let status = this.context.Buf.readInt32List()[0]; |
|
6671 this.sendChromeMessage({rilMessageType: "otastatuschange", |
|
6672 status: status}); |
|
6673 }; |
|
6674 RilObject.prototype[UNSOLICITED_CDMA_INFO_REC] = function UNSOLICITED_CDMA_INFO_REC(length) { |
|
6675 let record = this.context.CdmaPDUHelper.decodeInformationRecord(); |
|
6676 record.rilMessageType = "cdma-info-rec-received"; |
|
6677 this.sendChromeMessage(record); |
|
6678 }; |
|
6679 RilObject.prototype[UNSOLICITED_OEM_HOOK_RAW] = null; |
|
6680 RilObject.prototype[UNSOLICITED_RINGBACK_TONE] = null; |
|
6681 RilObject.prototype[UNSOLICITED_RESEND_INCALL_MUTE] = null; |
|
6682 RilObject.prototype[UNSOLICITED_CDMA_SUBSCRIPTION_SOURCE_CHANGED] = null; |
|
6683 RilObject.prototype[UNSOLICITED_CDMA_PRL_CHANGED] = function UNSOLICITED_CDMA_PRL_CHANGED(length) { |
|
6684 let version = this.context.Buf.readInt32List()[0]; |
|
6685 if (version !== this.iccInfo.prlVersion) { |
|
6686 this.iccInfo.prlVersion = version; |
|
6687 this.context.ICCUtilsHelper.handleICCInfoChange(); |
|
6688 } |
|
6689 }; |
|
6690 RilObject.prototype[UNSOLICITED_EXIT_EMERGENCY_CALLBACK_MODE] = function UNSOLICITED_EXIT_EMERGENCY_CALLBACK_MODE() { |
|
6691 this._handleChangedEmergencyCbMode(false); |
|
6692 }; |
|
6693 RilObject.prototype[UNSOLICITED_RIL_CONNECTED] = function UNSOLICITED_RIL_CONNECTED(length) { |
|
6694 // Prevent response id collision between UNSOLICITED_RIL_CONNECTED and |
|
6695 // UNSOLICITED_VOICE_RADIO_TECH_CHANGED for Akami on gingerbread branch. |
|
6696 if (!length) { |
|
6697 return; |
|
6698 } |
|
6699 |
|
6700 let version = this.context.Buf.readInt32List()[0]; |
|
6701 this.v5Legacy = (version < 5); |
|
6702 if (DEBUG) { |
|
6703 this.context.debug("Detected RIL version " + version); |
|
6704 this.context.debug("this.v5Legacy is " + this.v5Legacy); |
|
6705 } |
|
6706 |
|
6707 this.initRILState(); |
|
6708 // Always ensure that we are not in emergency callback mode when init. |
|
6709 this.exitEmergencyCbMode(); |
|
6710 // Reset radio in the case that b2g restart (or crash). |
|
6711 this.setRadioEnabled({enabled: false}); |
|
6712 }; |
|
6713 |
|
6714 /** |
|
6715 * This object exposes the functionality to parse and serialize PDU strings |
|
6716 * |
|
6717 * A PDU is a string containing a series of hexadecimally encoded octets |
|
6718 * or nibble-swapped binary-coded decimals (BCDs). It contains not only the |
|
6719 * message text but information about the sender, the SMS service center, |
|
6720 * timestamp, etc. |
|
6721 */ |
|
6722 function GsmPDUHelperObject(aContext) { |
|
6723 this.context = aContext; |
|
6724 } |
|
6725 GsmPDUHelperObject.prototype = { |
|
6726 context: null, |
|
6727 |
|
6728 /** |
|
6729 * Read one character (2 bytes) from a RIL string and decode as hex. |
|
6730 * |
|
6731 * @return the nibble as a number. |
|
6732 */ |
|
6733 readHexNibble: function() { |
|
6734 let nibble = this.context.Buf.readUint16(); |
|
6735 if (nibble >= 48 && nibble <= 57) { |
|
6736 nibble -= 48; // ASCII '0'..'9' |
|
6737 } else if (nibble >= 65 && nibble <= 70) { |
|
6738 nibble -= 55; // ASCII 'A'..'F' |
|
6739 } else if (nibble >= 97 && nibble <= 102) { |
|
6740 nibble -= 87; // ASCII 'a'..'f' |
|
6741 } else { |
|
6742 throw "Found invalid nibble during PDU parsing: " + |
|
6743 String.fromCharCode(nibble); |
|
6744 } |
|
6745 return nibble; |
|
6746 }, |
|
6747 |
|
6748 /** |
|
6749 * Encode a nibble as one hex character in a RIL string (2 bytes). |
|
6750 * |
|
6751 * @param nibble |
|
6752 * The nibble to encode (represented as a number) |
|
6753 */ |
|
6754 writeHexNibble: function(nibble) { |
|
6755 nibble &= 0x0f; |
|
6756 if (nibble < 10) { |
|
6757 nibble += 48; // ASCII '0' |
|
6758 } else { |
|
6759 nibble += 55; // ASCII 'A' |
|
6760 } |
|
6761 this.context.Buf.writeUint16(nibble); |
|
6762 }, |
|
6763 |
|
6764 /** |
|
6765 * Read a hex-encoded octet (two nibbles). |
|
6766 * |
|
6767 * @return the octet as a number. |
|
6768 */ |
|
6769 readHexOctet: function() { |
|
6770 return (this.readHexNibble() << 4) | this.readHexNibble(); |
|
6771 }, |
|
6772 |
|
6773 /** |
|
6774 * Write an octet as two hex-encoded nibbles. |
|
6775 * |
|
6776 * @param octet |
|
6777 * The octet (represented as a number) to encode. |
|
6778 */ |
|
6779 writeHexOctet: function(octet) { |
|
6780 this.writeHexNibble(octet >> 4); |
|
6781 this.writeHexNibble(octet); |
|
6782 }, |
|
6783 |
|
6784 /** |
|
6785 * Read an array of hex-encoded octets. |
|
6786 */ |
|
6787 readHexOctetArray: function(length) { |
|
6788 let array = new Uint8Array(length); |
|
6789 for (let i = 0; i < length; i++) { |
|
6790 array[i] = this.readHexOctet(); |
|
6791 } |
|
6792 return array; |
|
6793 }, |
|
6794 |
|
6795 /** |
|
6796 * Convert an octet (number) to a BCD number. |
|
6797 * |
|
6798 * Any nibbles that are not in the BCD range count as 0. |
|
6799 * |
|
6800 * @param octet |
|
6801 * The octet (a number, as returned by getOctet()) |
|
6802 * |
|
6803 * @return the corresponding BCD number. |
|
6804 */ |
|
6805 octetToBCD: function(octet) { |
|
6806 return ((octet & 0xf0) <= 0x90) * ((octet >> 4) & 0x0f) + |
|
6807 ((octet & 0x0f) <= 0x09) * (octet & 0x0f) * 10; |
|
6808 }, |
|
6809 |
|
6810 /** |
|
6811 * Convert a BCD number to an octet (number) |
|
6812 * |
|
6813 * Only take two digits with absolute value. |
|
6814 * |
|
6815 * @param bcd |
|
6816 * |
|
6817 * @return the corresponding octet. |
|
6818 */ |
|
6819 BCDToOctet: function(bcd) { |
|
6820 bcd = Math.abs(bcd); |
|
6821 return ((bcd % 10) << 4) + (Math.floor(bcd / 10) % 10); |
|
6822 }, |
|
6823 |
|
6824 /** |
|
6825 * Convert a semi-octet (number) to a GSM BCD char, or return empty string |
|
6826 * if invalid semiOctet and supressException is set to true. |
|
6827 * |
|
6828 * @param semiOctet |
|
6829 * Nibble to be converted to. |
|
6830 * @param [optional] supressException |
|
6831 * Supress exception if invalid semiOctet and supressException is set |
|
6832 * to true. |
|
6833 * |
|
6834 * @return GSM BCD char, or empty string. |
|
6835 */ |
|
6836 bcdChars: "0123456789*#,;", |
|
6837 semiOctetToBcdChar: function(semiOctet, supressException) { |
|
6838 if (semiOctet >= 14) { |
|
6839 if (supressException) { |
|
6840 return ""; |
|
6841 } else { |
|
6842 throw new RangeError(); |
|
6843 } |
|
6844 } |
|
6845 |
|
6846 return this.bcdChars.charAt(semiOctet); |
|
6847 }, |
|
6848 |
|
6849 /** |
|
6850 * Read a *swapped nibble* binary coded decimal (BCD) |
|
6851 * |
|
6852 * @param pairs |
|
6853 * Number of nibble *pairs* to read. |
|
6854 * |
|
6855 * @return the decimal as a number. |
|
6856 */ |
|
6857 readSwappedNibbleBcdNum: function(pairs) { |
|
6858 let number = 0; |
|
6859 for (let i = 0; i < pairs; i++) { |
|
6860 let octet = this.readHexOctet(); |
|
6861 // Ignore 'ff' octets as they're often used as filler. |
|
6862 if (octet == 0xff) { |
|
6863 continue; |
|
6864 } |
|
6865 // If the first nibble is an "F" , only the second nibble is to be taken |
|
6866 // into account. |
|
6867 if ((octet & 0xf0) == 0xf0) { |
|
6868 number *= 10; |
|
6869 number += octet & 0x0f; |
|
6870 continue; |
|
6871 } |
|
6872 number *= 100; |
|
6873 number += this.octetToBCD(octet); |
|
6874 } |
|
6875 return number; |
|
6876 }, |
|
6877 |
|
6878 /** |
|
6879 * Read a *swapped nibble* binary coded string (BCD) |
|
6880 * |
|
6881 * @param pairs |
|
6882 * Number of nibble *pairs* to read. |
|
6883 * @param [optional] supressException |
|
6884 * Supress exception if invalid semiOctet and supressException is set |
|
6885 * to true. |
|
6886 * |
|
6887 * @return The BCD string. |
|
6888 */ |
|
6889 readSwappedNibbleBcdString: function(pairs, supressException) { |
|
6890 let str = ""; |
|
6891 for (let i = 0; i < pairs; i++) { |
|
6892 let nibbleH = this.readHexNibble(); |
|
6893 let nibbleL = this.readHexNibble(); |
|
6894 if (nibbleL == 0x0F) { |
|
6895 break; |
|
6896 } |
|
6897 |
|
6898 str += this.semiOctetToBcdChar(nibbleL, supressException); |
|
6899 if (nibbleH != 0x0F) { |
|
6900 str += this.semiOctetToBcdChar(nibbleH, supressException); |
|
6901 } |
|
6902 } |
|
6903 |
|
6904 return str; |
|
6905 }, |
|
6906 |
|
6907 /** |
|
6908 * Write numerical data as swapped nibble BCD. |
|
6909 * |
|
6910 * @param data |
|
6911 * Data to write (as a string or a number) |
|
6912 */ |
|
6913 writeSwappedNibbleBCD: function(data) { |
|
6914 data = data.toString(); |
|
6915 if (data.length % 2) { |
|
6916 data += "F"; |
|
6917 } |
|
6918 let Buf = this.context.Buf; |
|
6919 for (let i = 0; i < data.length; i += 2) { |
|
6920 Buf.writeUint16(data.charCodeAt(i + 1)); |
|
6921 Buf.writeUint16(data.charCodeAt(i)); |
|
6922 } |
|
6923 }, |
|
6924 |
|
6925 /** |
|
6926 * Write numerical data as swapped nibble BCD. |
|
6927 * If the number of digit of data is even, add '0' at the beginning. |
|
6928 * |
|
6929 * @param data |
|
6930 * Data to write (as a string or a number) |
|
6931 */ |
|
6932 writeSwappedNibbleBCDNum: function(data) { |
|
6933 data = data.toString(); |
|
6934 if (data.length % 2) { |
|
6935 data = "0" + data; |
|
6936 } |
|
6937 let Buf = this.context.Buf; |
|
6938 for (let i = 0; i < data.length; i += 2) { |
|
6939 Buf.writeUint16(data.charCodeAt(i + 1)); |
|
6940 Buf.writeUint16(data.charCodeAt(i)); |
|
6941 } |
|
6942 }, |
|
6943 |
|
6944 /** |
|
6945 * Read user data, convert to septets, look up relevant characters in a |
|
6946 * 7-bit alphabet, and construct string. |
|
6947 * |
|
6948 * @param length |
|
6949 * Number of septets to read (*not* octets) |
|
6950 * @param paddingBits |
|
6951 * Number of padding bits in the first byte of user data. |
|
6952 * @param langIndex |
|
6953 * Table index used for normal 7-bit encoded character lookup. |
|
6954 * @param langShiftIndex |
|
6955 * Table index used for escaped 7-bit encoded character lookup. |
|
6956 * |
|
6957 * @return a string. |
|
6958 */ |
|
6959 readSeptetsToString: function(length, paddingBits, langIndex, langShiftIndex) { |
|
6960 let ret = ""; |
|
6961 let byteLength = Math.ceil((length * 7 + paddingBits) / 8); |
|
6962 |
|
6963 /** |
|
6964 * |<- last byte in header ->| |
|
6965 * |<- incompleteBits ->|<- last header septet->| |
|
6966 * +===7===|===6===|===5===|===4===|===3===|===2===|===1===|===0===| |
|
6967 * |
|
6968 * |<- 1st byte in user data ->| |
|
6969 * |<- data septet 1 ->|<-paddingBits->| |
|
6970 * +===7===|===6===|===5===|===4===|===3===|===2===|===1===|===0===| |
|
6971 * |
|
6972 * |<- 2nd byte in user data ->| |
|
6973 * |<- data spetet 2 ->|<-ds1->| |
|
6974 * +===7===|===6===|===5===|===4===|===3===|===2===|===1===|===0===| |
|
6975 */ |
|
6976 let data = 0; |
|
6977 let dataBits = 0; |
|
6978 if (paddingBits) { |
|
6979 data = this.readHexOctet() >> paddingBits; |
|
6980 dataBits = 8 - paddingBits; |
|
6981 --byteLength; |
|
6982 } |
|
6983 |
|
6984 let escapeFound = false; |
|
6985 const langTable = PDU_NL_LOCKING_SHIFT_TABLES[langIndex]; |
|
6986 const langShiftTable = PDU_NL_SINGLE_SHIFT_TABLES[langShiftIndex]; |
|
6987 do { |
|
6988 // Read as much as fits in 32bit word |
|
6989 let bytesToRead = Math.min(byteLength, dataBits ? 3 : 4); |
|
6990 for (let i = 0; i < bytesToRead; i++) { |
|
6991 data |= this.readHexOctet() << dataBits; |
|
6992 dataBits += 8; |
|
6993 --byteLength; |
|
6994 } |
|
6995 |
|
6996 // Consume available full septets |
|
6997 for (; dataBits >= 7; dataBits -= 7) { |
|
6998 let septet = data & 0x7F; |
|
6999 data >>>= 7; |
|
7000 |
|
7001 if (escapeFound) { |
|
7002 escapeFound = false; |
|
7003 if (septet == PDU_NL_EXTENDED_ESCAPE) { |
|
7004 // According to 3GPP TS 23.038, section 6.2.1.1, NOTE 1, "On |
|
7005 // receipt of this code, a receiving entity shall display a space |
|
7006 // until another extensiion table is defined." |
|
7007 ret += " "; |
|
7008 } else if (septet == PDU_NL_RESERVED_CONTROL) { |
|
7009 // According to 3GPP TS 23.038 B.2, "This code represents a control |
|
7010 // character and therefore must not be used for language specific |
|
7011 // characters." |
|
7012 ret += " "; |
|
7013 } else { |
|
7014 ret += langShiftTable[septet]; |
|
7015 } |
|
7016 } else if (septet == PDU_NL_EXTENDED_ESCAPE) { |
|
7017 escapeFound = true; |
|
7018 |
|
7019 // <escape> is not an effective character |
|
7020 --length; |
|
7021 } else { |
|
7022 ret += langTable[septet]; |
|
7023 } |
|
7024 } |
|
7025 } while (byteLength); |
|
7026 |
|
7027 if (ret.length != length) { |
|
7028 /** |
|
7029 * If num of effective characters does not equal to the length of read |
|
7030 * string, cut the tail off. This happens when the last octet of user |
|
7031 * data has following layout: |
|
7032 * |
|
7033 * |<- penultimate octet in user data ->| |
|
7034 * |<- data septet N ->|<- dsN-1 ->| |
|
7035 * +===7===|===6===|===5===|===4===|===3===|===2===|===1===|===0===| |
|
7036 * |
|
7037 * |<- last octet in user data ->| |
|
7038 * |<- fill bits ->|<-dsN->| |
|
7039 * +===7===|===6===|===5===|===4===|===3===|===2===|===1===|===0===| |
|
7040 * |
|
7041 * The fill bits in the last octet may happen to form a full septet and |
|
7042 * be appended at the end of result string. |
|
7043 */ |
|
7044 ret = ret.slice(0, length); |
|
7045 } |
|
7046 return ret; |
|
7047 }, |
|
7048 |
|
7049 writeStringAsSeptets: function(message, paddingBits, langIndex, langShiftIndex) { |
|
7050 const langTable = PDU_NL_LOCKING_SHIFT_TABLES[langIndex]; |
|
7051 const langShiftTable = PDU_NL_SINGLE_SHIFT_TABLES[langShiftIndex]; |
|
7052 |
|
7053 let dataBits = paddingBits; |
|
7054 let data = 0; |
|
7055 for (let i = 0; i < message.length; i++) { |
|
7056 let c = message.charAt(i); |
|
7057 let septet = langTable.indexOf(c); |
|
7058 if (septet == PDU_NL_EXTENDED_ESCAPE) { |
|
7059 continue; |
|
7060 } |
|
7061 |
|
7062 if (septet >= 0) { |
|
7063 data |= septet << dataBits; |
|
7064 dataBits += 7; |
|
7065 } else { |
|
7066 septet = langShiftTable.indexOf(c); |
|
7067 if (septet == -1) { |
|
7068 throw new Error("'" + c + "' is not in 7 bit alphabet " |
|
7069 + langIndex + ":" + langShiftIndex + "!"); |
|
7070 } |
|
7071 |
|
7072 if (septet == PDU_NL_RESERVED_CONTROL) { |
|
7073 continue; |
|
7074 } |
|
7075 |
|
7076 data |= PDU_NL_EXTENDED_ESCAPE << dataBits; |
|
7077 dataBits += 7; |
|
7078 data |= septet << dataBits; |
|
7079 dataBits += 7; |
|
7080 } |
|
7081 |
|
7082 for (; dataBits >= 8; dataBits -= 8) { |
|
7083 this.writeHexOctet(data & 0xFF); |
|
7084 data >>>= 8; |
|
7085 } |
|
7086 } |
|
7087 |
|
7088 if (dataBits !== 0) { |
|
7089 this.writeHexOctet(data & 0xFF); |
|
7090 } |
|
7091 }, |
|
7092 |
|
7093 /** |
|
7094 * Read user data and decode as a UCS2 string. |
|
7095 * |
|
7096 * @param numOctets |
|
7097 * Number of octets to be read as UCS2 string. |
|
7098 * |
|
7099 * @return a string. |
|
7100 */ |
|
7101 readUCS2String: function(numOctets) { |
|
7102 let str = ""; |
|
7103 let length = numOctets / 2; |
|
7104 for (let i = 0; i < length; ++i) { |
|
7105 let code = (this.readHexOctet() << 8) | this.readHexOctet(); |
|
7106 str += String.fromCharCode(code); |
|
7107 } |
|
7108 |
|
7109 if (DEBUG) this.context.debug("Read UCS2 string: " + str); |
|
7110 |
|
7111 return str; |
|
7112 }, |
|
7113 |
|
7114 /** |
|
7115 * Write user data as a UCS2 string. |
|
7116 * |
|
7117 * @param message |
|
7118 * Message string to encode as UCS2 in hex-encoded octets. |
|
7119 */ |
|
7120 writeUCS2String: function(message) { |
|
7121 for (let i = 0; i < message.length; ++i) { |
|
7122 let code = message.charCodeAt(i); |
|
7123 this.writeHexOctet((code >> 8) & 0xFF); |
|
7124 this.writeHexOctet(code & 0xFF); |
|
7125 } |
|
7126 }, |
|
7127 |
|
7128 /** |
|
7129 * Read 1 + UDHL octets and construct user data header. |
|
7130 * |
|
7131 * @param msg |
|
7132 * message object for output. |
|
7133 * |
|
7134 * @see 3GPP TS 23.040 9.2.3.24 |
|
7135 */ |
|
7136 readUserDataHeader: function(msg) { |
|
7137 /** |
|
7138 * A header object with properties contained in received message. |
|
7139 * The properties set include: |
|
7140 * |
|
7141 * length: totoal length of the header, default 0. |
|
7142 * langIndex: used locking shift table index, default |
|
7143 * PDU_NL_IDENTIFIER_DEFAULT. |
|
7144 * langShiftIndex: used locking shift table index, default |
|
7145 * PDU_NL_IDENTIFIER_DEFAULT. |
|
7146 * |
|
7147 */ |
|
7148 let header = { |
|
7149 length: 0, |
|
7150 langIndex: PDU_NL_IDENTIFIER_DEFAULT, |
|
7151 langShiftIndex: PDU_NL_IDENTIFIER_DEFAULT |
|
7152 }; |
|
7153 |
|
7154 header.length = this.readHexOctet(); |
|
7155 if (DEBUG) this.context.debug("Read UDH length: " + header.length); |
|
7156 |
|
7157 let dataAvailable = header.length; |
|
7158 while (dataAvailable >= 2) { |
|
7159 let id = this.readHexOctet(); |
|
7160 let length = this.readHexOctet(); |
|
7161 if (DEBUG) this.context.debug("Read UDH id: " + id + ", length: " + length); |
|
7162 |
|
7163 dataAvailable -= 2; |
|
7164 |
|
7165 switch (id) { |
|
7166 case PDU_IEI_CONCATENATED_SHORT_MESSAGES_8BIT: { |
|
7167 let ref = this.readHexOctet(); |
|
7168 let max = this.readHexOctet(); |
|
7169 let seq = this.readHexOctet(); |
|
7170 dataAvailable -= 3; |
|
7171 if (max && seq && (seq <= max)) { |
|
7172 header.segmentRef = ref; |
|
7173 header.segmentMaxSeq = max; |
|
7174 header.segmentSeq = seq; |
|
7175 } |
|
7176 break; |
|
7177 } |
|
7178 case PDU_IEI_APPLICATION_PORT_ADDRESSING_SCHEME_8BIT: { |
|
7179 let dstp = this.readHexOctet(); |
|
7180 let orip = this.readHexOctet(); |
|
7181 dataAvailable -= 2; |
|
7182 if ((dstp < PDU_APA_RESERVED_8BIT_PORTS) |
|
7183 || (orip < PDU_APA_RESERVED_8BIT_PORTS)) { |
|
7184 // 3GPP TS 23.040 clause 9.2.3.24.3: "A receiving entity shall |
|
7185 // ignore any information element where the value of the |
|
7186 // Information-Element-Data is Reserved or not supported" |
|
7187 break; |
|
7188 } |
|
7189 header.destinationPort = dstp; |
|
7190 header.originatorPort = orip; |
|
7191 break; |
|
7192 } |
|
7193 case PDU_IEI_APPLICATION_PORT_ADDRESSING_SCHEME_16BIT: { |
|
7194 let dstp = (this.readHexOctet() << 8) | this.readHexOctet(); |
|
7195 let orip = (this.readHexOctet() << 8) | this.readHexOctet(); |
|
7196 dataAvailable -= 4; |
|
7197 // 3GPP TS 23.040 clause 9.2.3.24.4: "A receiving entity shall |
|
7198 // ignore any information element where the value of the |
|
7199 // Information-Element-Data is Reserved or not supported" |
|
7200 if ((dstp < PDU_APA_VALID_16BIT_PORTS) |
|
7201 && (orip < PDU_APA_VALID_16BIT_PORTS)) { |
|
7202 header.destinationPort = dstp; |
|
7203 header.originatorPort = orip; |
|
7204 } |
|
7205 break; |
|
7206 } |
|
7207 case PDU_IEI_CONCATENATED_SHORT_MESSAGES_16BIT: { |
|
7208 let ref = (this.readHexOctet() << 8) | this.readHexOctet(); |
|
7209 let max = this.readHexOctet(); |
|
7210 let seq = this.readHexOctet(); |
|
7211 dataAvailable -= 4; |
|
7212 if (max && seq && (seq <= max)) { |
|
7213 header.segmentRef = ref; |
|
7214 header.segmentMaxSeq = max; |
|
7215 header.segmentSeq = seq; |
|
7216 } |
|
7217 break; |
|
7218 } |
|
7219 case PDU_IEI_NATIONAL_LANGUAGE_SINGLE_SHIFT: |
|
7220 let langShiftIndex = this.readHexOctet(); |
|
7221 --dataAvailable; |
|
7222 if (langShiftIndex < PDU_NL_SINGLE_SHIFT_TABLES.length) { |
|
7223 header.langShiftIndex = langShiftIndex; |
|
7224 } |
|
7225 break; |
|
7226 case PDU_IEI_NATIONAL_LANGUAGE_LOCKING_SHIFT: |
|
7227 let langIndex = this.readHexOctet(); |
|
7228 --dataAvailable; |
|
7229 if (langIndex < PDU_NL_LOCKING_SHIFT_TABLES.length) { |
|
7230 header.langIndex = langIndex; |
|
7231 } |
|
7232 break; |
|
7233 case PDU_IEI_SPECIAL_SMS_MESSAGE_INDICATION: |
|
7234 let msgInd = this.readHexOctet() & 0xFF; |
|
7235 let msgCount = this.readHexOctet(); |
|
7236 dataAvailable -= 2; |
|
7237 |
|
7238 |
|
7239 /* |
|
7240 * TS 23.040 V6.8.1 Sec 9.2.3.24.2 |
|
7241 * bits 1 0 : basic message indication type |
|
7242 * bits 4 3 2 : extended message indication type |
|
7243 * bits 6 5 : Profile id |
|
7244 * bit 7 : storage type |
|
7245 */ |
|
7246 let storeType = msgInd & PDU_MWI_STORE_TYPE_BIT; |
|
7247 let mwi = msg.mwi; |
|
7248 if (!mwi) { |
|
7249 mwi = msg.mwi = {}; |
|
7250 } |
|
7251 |
|
7252 if (storeType == PDU_MWI_STORE_TYPE_STORE) { |
|
7253 // Store message because TP_UDH indicates so, note this may override |
|
7254 // the setting in DCS, but that is expected |
|
7255 mwi.discard = false; |
|
7256 } else if (mwi.discard === undefined) { |
|
7257 // storeType == PDU_MWI_STORE_TYPE_DISCARD |
|
7258 // only override mwi.discard here if it hasn't already been set |
|
7259 mwi.discard = true; |
|
7260 } |
|
7261 |
|
7262 mwi.msgCount = msgCount & 0xFF; |
|
7263 mwi.active = mwi.msgCount > 0; |
|
7264 |
|
7265 if (DEBUG) { |
|
7266 this.context.debug("MWI in TP_UDH received: " + JSON.stringify(mwi)); |
|
7267 } |
|
7268 |
|
7269 break; |
|
7270 default: |
|
7271 if (DEBUG) { |
|
7272 this.context.debug("readUserDataHeader: unsupported IEI(" + id + |
|
7273 "), " + length + " bytes."); |
|
7274 } |
|
7275 |
|
7276 // Read out unsupported data |
|
7277 if (length) { |
|
7278 let octets; |
|
7279 if (DEBUG) octets = new Uint8Array(length); |
|
7280 |
|
7281 for (let i = 0; i < length; i++) { |
|
7282 let octet = this.readHexOctet(); |
|
7283 if (DEBUG) octets[i] = octet; |
|
7284 } |
|
7285 dataAvailable -= length; |
|
7286 |
|
7287 if (DEBUG) { |
|
7288 this.context.debug("readUserDataHeader: " + Array.slice(octets)); |
|
7289 } |
|
7290 } |
|
7291 break; |
|
7292 } |
|
7293 } |
|
7294 |
|
7295 if (dataAvailable !== 0) { |
|
7296 throw new Error("Illegal user data header found!"); |
|
7297 } |
|
7298 |
|
7299 msg.header = header; |
|
7300 }, |
|
7301 |
|
7302 /** |
|
7303 * Write out user data header. |
|
7304 * |
|
7305 * @param options |
|
7306 * Options containing information for user data header write-out. The |
|
7307 * `userDataHeaderLength` property must be correctly pre-calculated. |
|
7308 */ |
|
7309 writeUserDataHeader: function(options) { |
|
7310 this.writeHexOctet(options.userDataHeaderLength); |
|
7311 |
|
7312 if (options.segmentMaxSeq > 1) { |
|
7313 if (options.segmentRef16Bit) { |
|
7314 this.writeHexOctet(PDU_IEI_CONCATENATED_SHORT_MESSAGES_16BIT); |
|
7315 this.writeHexOctet(4); |
|
7316 this.writeHexOctet((options.segmentRef >> 8) & 0xFF); |
|
7317 } else { |
|
7318 this.writeHexOctet(PDU_IEI_CONCATENATED_SHORT_MESSAGES_8BIT); |
|
7319 this.writeHexOctet(3); |
|
7320 } |
|
7321 this.writeHexOctet(options.segmentRef & 0xFF); |
|
7322 this.writeHexOctet(options.segmentMaxSeq & 0xFF); |
|
7323 this.writeHexOctet(options.segmentSeq & 0xFF); |
|
7324 } |
|
7325 |
|
7326 if (options.dcs == PDU_DCS_MSG_CODING_7BITS_ALPHABET) { |
|
7327 if (options.langIndex != PDU_NL_IDENTIFIER_DEFAULT) { |
|
7328 this.writeHexOctet(PDU_IEI_NATIONAL_LANGUAGE_LOCKING_SHIFT); |
|
7329 this.writeHexOctet(1); |
|
7330 this.writeHexOctet(options.langIndex); |
|
7331 } |
|
7332 |
|
7333 if (options.langShiftIndex != PDU_NL_IDENTIFIER_DEFAULT) { |
|
7334 this.writeHexOctet(PDU_IEI_NATIONAL_LANGUAGE_SINGLE_SHIFT); |
|
7335 this.writeHexOctet(1); |
|
7336 this.writeHexOctet(options.langShiftIndex); |
|
7337 } |
|
7338 } |
|
7339 }, |
|
7340 |
|
7341 /** |
|
7342 * Read SM-TL Address. |
|
7343 * |
|
7344 * @param len |
|
7345 * Length of useful semi-octets within the Address-Value field. For |
|
7346 * example, the lenth of "12345" should be 5, and 4 for "1234". |
|
7347 * |
|
7348 * @see 3GPP TS 23.040 9.1.2.5 |
|
7349 */ |
|
7350 readAddress: function(len) { |
|
7351 // Address Length |
|
7352 if (!len || (len < 0)) { |
|
7353 if (DEBUG) { |
|
7354 this.context.debug("PDU error: invalid sender address length: " + len); |
|
7355 } |
|
7356 return null; |
|
7357 } |
|
7358 if (len % 2 == 1) { |
|
7359 len += 1; |
|
7360 } |
|
7361 if (DEBUG) this.context.debug("PDU: Going to read address: " + len); |
|
7362 |
|
7363 // Type-of-Address |
|
7364 let toa = this.readHexOctet(); |
|
7365 let addr = ""; |
|
7366 |
|
7367 if ((toa & 0xF0) == PDU_TOA_ALPHANUMERIC) { |
|
7368 addr = this.readSeptetsToString(Math.floor(len * 4 / 7), 0, |
|
7369 PDU_NL_IDENTIFIER_DEFAULT , PDU_NL_IDENTIFIER_DEFAULT ); |
|
7370 return addr; |
|
7371 } |
|
7372 addr = this.readSwappedNibbleBcdString(len / 2); |
|
7373 if (addr.length <= 0) { |
|
7374 if (DEBUG) this.context.debug("PDU error: no number provided"); |
|
7375 return null; |
|
7376 } |
|
7377 if ((toa & 0xF0) == (PDU_TOA_INTERNATIONAL)) { |
|
7378 addr = '+' + addr; |
|
7379 } |
|
7380 |
|
7381 return addr; |
|
7382 }, |
|
7383 |
|
7384 /** |
|
7385 * Read TP-Protocol-Indicator(TP-PID). |
|
7386 * |
|
7387 * @param msg |
|
7388 * message object for output. |
|
7389 * |
|
7390 * @see 3GPP TS 23.040 9.2.3.9 |
|
7391 */ |
|
7392 readProtocolIndicator: function(msg) { |
|
7393 // `The MS shall interpret reserved, obsolete, or unsupported values as the |
|
7394 // value 00000000 but shall store them exactly as received.` |
|
7395 msg.pid = this.readHexOctet(); |
|
7396 |
|
7397 msg.epid = msg.pid; |
|
7398 switch (msg.epid & 0xC0) { |
|
7399 case 0x40: |
|
7400 // Bit 7..0 = 01xxxxxx |
|
7401 switch (msg.epid) { |
|
7402 case PDU_PID_SHORT_MESSAGE_TYPE_0: |
|
7403 case PDU_PID_ANSI_136_R_DATA: |
|
7404 case PDU_PID_USIM_DATA_DOWNLOAD: |
|
7405 return; |
|
7406 } |
|
7407 break; |
|
7408 } |
|
7409 |
|
7410 msg.epid = PDU_PID_DEFAULT; |
|
7411 }, |
|
7412 |
|
7413 /** |
|
7414 * Read TP-Data-Coding-Scheme(TP-DCS) |
|
7415 * |
|
7416 * @param msg |
|
7417 * message object for output. |
|
7418 * |
|
7419 * @see 3GPP TS 23.040 9.2.3.10, 3GPP TS 23.038 4. |
|
7420 */ |
|
7421 readDataCodingScheme: function(msg) { |
|
7422 let dcs = this.readHexOctet(); |
|
7423 if (DEBUG) this.context.debug("PDU: read SMS dcs: " + dcs); |
|
7424 |
|
7425 // No message class by default. |
|
7426 let messageClass = PDU_DCS_MSG_CLASS_NORMAL; |
|
7427 // 7 bit is the default fallback encoding. |
|
7428 let encoding = PDU_DCS_MSG_CODING_7BITS_ALPHABET; |
|
7429 switch (dcs & PDU_DCS_CODING_GROUP_BITS) { |
|
7430 case 0x40: // bits 7..4 = 01xx |
|
7431 case 0x50: |
|
7432 case 0x60: |
|
7433 case 0x70: |
|
7434 // Bit 5..0 are coded exactly the same as Group 00xx |
|
7435 case 0x00: // bits 7..4 = 00xx |
|
7436 case 0x10: |
|
7437 case 0x20: |
|
7438 case 0x30: |
|
7439 if (dcs & 0x10) { |
|
7440 messageClass = dcs & PDU_DCS_MSG_CLASS_BITS; |
|
7441 } |
|
7442 switch (dcs & 0x0C) { |
|
7443 case 0x4: |
|
7444 encoding = PDU_DCS_MSG_CODING_8BITS_ALPHABET; |
|
7445 break; |
|
7446 case 0x8: |
|
7447 encoding = PDU_DCS_MSG_CODING_16BITS_ALPHABET; |
|
7448 break; |
|
7449 } |
|
7450 break; |
|
7451 |
|
7452 case 0xE0: // bits 7..4 = 1110 |
|
7453 encoding = PDU_DCS_MSG_CODING_16BITS_ALPHABET; |
|
7454 // Bit 3..0 are coded exactly the same as Message Waiting Indication |
|
7455 // Group 1101. |
|
7456 // Fall through. |
|
7457 case 0xC0: // bits 7..4 = 1100 |
|
7458 case 0xD0: // bits 7..4 = 1101 |
|
7459 // Indiciates voicemail indicator set or clear |
|
7460 let active = (dcs & PDU_DCS_MWI_ACTIVE_BITS) == PDU_DCS_MWI_ACTIVE_VALUE; |
|
7461 |
|
7462 // If TP-UDH is present, these values will be overwritten |
|
7463 switch (dcs & PDU_DCS_MWI_TYPE_BITS) { |
|
7464 case PDU_DCS_MWI_TYPE_VOICEMAIL: |
|
7465 let mwi = msg.mwi; |
|
7466 if (!mwi) { |
|
7467 mwi = msg.mwi = {}; |
|
7468 } |
|
7469 |
|
7470 mwi.active = active; |
|
7471 mwi.discard = (dcs & PDU_DCS_CODING_GROUP_BITS) == 0xC0; |
|
7472 mwi.msgCount = active ? GECKO_VOICEMAIL_MESSAGE_COUNT_UNKNOWN : 0; |
|
7473 |
|
7474 if (DEBUG) { |
|
7475 this.context.debug("MWI in DCS received for voicemail: " + |
|
7476 JSON.stringify(mwi)); |
|
7477 } |
|
7478 break; |
|
7479 case PDU_DCS_MWI_TYPE_FAX: |
|
7480 if (DEBUG) this.context.debug("MWI in DCS received for fax"); |
|
7481 break; |
|
7482 case PDU_DCS_MWI_TYPE_EMAIL: |
|
7483 if (DEBUG) this.context.debug("MWI in DCS received for email"); |
|
7484 break; |
|
7485 default: |
|
7486 if (DEBUG) this.context.debug("MWI in DCS received for \"other\""); |
|
7487 break; |
|
7488 } |
|
7489 break; |
|
7490 |
|
7491 case 0xF0: // bits 7..4 = 1111 |
|
7492 if (dcs & 0x04) { |
|
7493 encoding = PDU_DCS_MSG_CODING_8BITS_ALPHABET; |
|
7494 } |
|
7495 messageClass = dcs & PDU_DCS_MSG_CLASS_BITS; |
|
7496 break; |
|
7497 |
|
7498 default: |
|
7499 // Falling back to default encoding. |
|
7500 break; |
|
7501 } |
|
7502 |
|
7503 msg.dcs = dcs; |
|
7504 msg.encoding = encoding; |
|
7505 msg.messageClass = GECKO_SMS_MESSAGE_CLASSES[messageClass]; |
|
7506 |
|
7507 if (DEBUG) this.context.debug("PDU: message encoding is " + encoding + " bit."); |
|
7508 }, |
|
7509 |
|
7510 /** |
|
7511 * Read GSM TP-Service-Centre-Time-Stamp(TP-SCTS). |
|
7512 * |
|
7513 * @see 3GPP TS 23.040 9.2.3.11 |
|
7514 */ |
|
7515 readTimestamp: function() { |
|
7516 let year = this.readSwappedNibbleBcdNum(1) + PDU_TIMESTAMP_YEAR_OFFSET; |
|
7517 let month = this.readSwappedNibbleBcdNum(1) - 1; |
|
7518 let day = this.readSwappedNibbleBcdNum(1); |
|
7519 let hour = this.readSwappedNibbleBcdNum(1); |
|
7520 let minute = this.readSwappedNibbleBcdNum(1); |
|
7521 let second = this.readSwappedNibbleBcdNum(1); |
|
7522 let timestamp = Date.UTC(year, month, day, hour, minute, second); |
|
7523 |
|
7524 // If the most significant bit of the least significant nibble is 1, |
|
7525 // the timezone offset is negative (fourth bit from the right => 0x08): |
|
7526 // localtime = UTC + tzOffset |
|
7527 // therefore |
|
7528 // UTC = localtime - tzOffset |
|
7529 let tzOctet = this.readHexOctet(); |
|
7530 let tzOffset = this.octetToBCD(tzOctet & ~0x08) * 15 * 60 * 1000; |
|
7531 tzOffset = (tzOctet & 0x08) ? -tzOffset : tzOffset; |
|
7532 timestamp -= tzOffset; |
|
7533 |
|
7534 return timestamp; |
|
7535 }, |
|
7536 |
|
7537 /** |
|
7538 * Write GSM TP-Service-Centre-Time-Stamp(TP-SCTS). |
|
7539 * |
|
7540 * @see 3GPP TS 23.040 9.2.3.11 |
|
7541 */ |
|
7542 writeTimestamp: function(date) { |
|
7543 this.writeSwappedNibbleBCDNum(date.getFullYear() - PDU_TIMESTAMP_YEAR_OFFSET); |
|
7544 |
|
7545 // The value returned by getMonth() is an integer between 0 and 11. |
|
7546 // 0 is corresponds to January, 1 to February, and so on. |
|
7547 this.writeSwappedNibbleBCDNum(date.getMonth() + 1); |
|
7548 this.writeSwappedNibbleBCDNum(date.getDate()); |
|
7549 this.writeSwappedNibbleBCDNum(date.getHours()); |
|
7550 this.writeSwappedNibbleBCDNum(date.getMinutes()); |
|
7551 this.writeSwappedNibbleBCDNum(date.getSeconds()); |
|
7552 |
|
7553 // the value returned by getTimezoneOffset() is the difference, |
|
7554 // in minutes, between UTC and local time. |
|
7555 // For example, if your time zone is UTC+10 (Australian Eastern Standard Time), |
|
7556 // -600 will be returned. |
|
7557 // In TS 23.040 9.2.3.11, the Time Zone field of TP-SCTS indicates |
|
7558 // the different between the local time and GMT. |
|
7559 // And expressed in quarters of an hours. (so need to divid by 15) |
|
7560 let zone = date.getTimezoneOffset() / 15; |
|
7561 let octet = this.BCDToOctet(zone); |
|
7562 |
|
7563 // the bit3 of the Time Zone field represents the algebraic sign. |
|
7564 // (0: positive, 1: negative). |
|
7565 // For example, if the time zone is -0800 GMT, |
|
7566 // 480 will be returned by getTimezoneOffset(). |
|
7567 // In this case, need to mark sign bit as 1. => 0x08 |
|
7568 if (zone > 0) { |
|
7569 octet = octet | 0x08; |
|
7570 } |
|
7571 this.writeHexOctet(octet); |
|
7572 }, |
|
7573 |
|
7574 /** |
|
7575 * User data can be 7 bit (default alphabet) data, 8 bit data, or 16 bit |
|
7576 * (UCS2) data. |
|
7577 * |
|
7578 * @param msg |
|
7579 * message object for output. |
|
7580 * @param length |
|
7581 * length of user data to read in octets. |
|
7582 */ |
|
7583 readUserData: function(msg, length) { |
|
7584 if (DEBUG) { |
|
7585 this.context.debug("Reading " + length + " bytes of user data."); |
|
7586 } |
|
7587 |
|
7588 let paddingBits = 0; |
|
7589 if (msg.udhi) { |
|
7590 this.readUserDataHeader(msg); |
|
7591 |
|
7592 if (msg.encoding == PDU_DCS_MSG_CODING_7BITS_ALPHABET) { |
|
7593 let headerBits = (msg.header.length + 1) * 8; |
|
7594 let headerSeptets = Math.ceil(headerBits / 7); |
|
7595 |
|
7596 length -= headerSeptets; |
|
7597 paddingBits = headerSeptets * 7 - headerBits; |
|
7598 } else { |
|
7599 length -= (msg.header.length + 1); |
|
7600 } |
|
7601 } |
|
7602 |
|
7603 if (DEBUG) { |
|
7604 this.context.debug("After header, " + length + " septets left of user data"); |
|
7605 } |
|
7606 |
|
7607 msg.body = null; |
|
7608 msg.data = null; |
|
7609 switch (msg.encoding) { |
|
7610 case PDU_DCS_MSG_CODING_7BITS_ALPHABET: |
|
7611 // 7 bit encoding allows 140 octets, which means 160 characters |
|
7612 // ((140x8) / 7 = 160 chars) |
|
7613 if (length > PDU_MAX_USER_DATA_7BIT) { |
|
7614 if (DEBUG) { |
|
7615 this.context.debug("PDU error: user data is too long: " + length); |
|
7616 } |
|
7617 break; |
|
7618 } |
|
7619 |
|
7620 let langIndex = msg.udhi ? msg.header.langIndex : PDU_NL_IDENTIFIER_DEFAULT; |
|
7621 let langShiftIndex = msg.udhi ? msg.header.langShiftIndex : PDU_NL_IDENTIFIER_DEFAULT; |
|
7622 msg.body = this.readSeptetsToString(length, paddingBits, langIndex, |
|
7623 langShiftIndex); |
|
7624 break; |
|
7625 case PDU_DCS_MSG_CODING_8BITS_ALPHABET: |
|
7626 msg.data = this.readHexOctetArray(length); |
|
7627 break; |
|
7628 case PDU_DCS_MSG_CODING_16BITS_ALPHABET: |
|
7629 msg.body = this.readUCS2String(length); |
|
7630 break; |
|
7631 } |
|
7632 }, |
|
7633 |
|
7634 /** |
|
7635 * Read extra parameters if TP-PI is set. |
|
7636 * |
|
7637 * @param msg |
|
7638 * message object for output. |
|
7639 */ |
|
7640 readExtraParams: function(msg) { |
|
7641 // Because each PDU octet is converted to two UCS2 char2, we should always |
|
7642 // get even messageStringLength in this#_processReceivedSms(). So, we'll |
|
7643 // always need two delimitors at the end. |
|
7644 if (this.context.Buf.getReadAvailable() <= 4) { |
|
7645 return; |
|
7646 } |
|
7647 |
|
7648 // TP-Parameter-Indicator |
|
7649 let pi; |
|
7650 do { |
|
7651 // `The most significant bit in octet 1 and any other TP-PI octets which |
|
7652 // may be added later is reserved as an extension bit which when set to a |
|
7653 // 1 shall indicate that another TP-PI octet follows immediately |
|
7654 // afterwards.` ~ 3GPP TS 23.040 9.2.3.27 |
|
7655 pi = this.readHexOctet(); |
|
7656 } while (pi & PDU_PI_EXTENSION); |
|
7657 |
|
7658 // `If the TP-UDL bit is set to "1" but the TP-DCS bit is set to "0" then |
|
7659 // the receiving entity shall for TP-DCS assume a value of 0x00, i.e. the |
|
7660 // 7bit default alphabet.` ~ 3GPP 23.040 9.2.3.27 |
|
7661 msg.dcs = 0; |
|
7662 msg.encoding = PDU_DCS_MSG_CODING_7BITS_ALPHABET; |
|
7663 |
|
7664 // TP-Protocol-Identifier |
|
7665 if (pi & PDU_PI_PROTOCOL_IDENTIFIER) { |
|
7666 this.readProtocolIndicator(msg); |
|
7667 } |
|
7668 // TP-Data-Coding-Scheme |
|
7669 if (pi & PDU_PI_DATA_CODING_SCHEME) { |
|
7670 this.readDataCodingScheme(msg); |
|
7671 } |
|
7672 // TP-User-Data-Length |
|
7673 if (pi & PDU_PI_USER_DATA_LENGTH) { |
|
7674 let userDataLength = this.readHexOctet(); |
|
7675 this.readUserData(msg, userDataLength); |
|
7676 } |
|
7677 }, |
|
7678 |
|
7679 /** |
|
7680 * Read and decode a PDU-encoded message from the stream. |
|
7681 * |
|
7682 * TODO: add some basic sanity checks like: |
|
7683 * - do we have the minimum number of chars available |
|
7684 */ |
|
7685 readMessage: function() { |
|
7686 // An empty message object. This gets filled below and then returned. |
|
7687 let msg = { |
|
7688 // D:DELIVER, DR:DELIVER-REPORT, S:SUBMIT, SR:SUBMIT-REPORT, |
|
7689 // ST:STATUS-REPORT, C:COMMAND |
|
7690 // M:Mandatory, O:Optional, X:Unavailable |
|
7691 // D DR S SR ST C |
|
7692 SMSC: null, // M M M M M M |
|
7693 mti: null, // M M M M M M |
|
7694 udhi: null, // M M O M M M |
|
7695 sender: null, // M X X X X X |
|
7696 recipient: null, // X X M X M M |
|
7697 pid: null, // M O M O O M |
|
7698 epid: null, // M O M O O M |
|
7699 dcs: null, // M O M O O X |
|
7700 mwi: null, // O O O O O O |
|
7701 replace: false, // O O O O O O |
|
7702 header: null, // M M O M M M |
|
7703 body: null, // M O M O O O |
|
7704 data: null, // M O M O O O |
|
7705 sentTimestamp: null, // M X X X X X |
|
7706 status: null, // X X X X M X |
|
7707 scts: null, // X X X M M X |
|
7708 dt: null, // X X X X M X |
|
7709 }; |
|
7710 |
|
7711 // SMSC info |
|
7712 let smscLength = this.readHexOctet(); |
|
7713 if (smscLength > 0) { |
|
7714 let smscTypeOfAddress = this.readHexOctet(); |
|
7715 // Subtract the type-of-address octet we just read from the length. |
|
7716 msg.SMSC = this.readSwappedNibbleBcdString(smscLength - 1); |
|
7717 if ((smscTypeOfAddress >> 4) == (PDU_TOA_INTERNATIONAL >> 4)) { |
|
7718 msg.SMSC = '+' + msg.SMSC; |
|
7719 } |
|
7720 } |
|
7721 |
|
7722 // First octet of this SMS-DELIVER or SMS-SUBMIT message |
|
7723 let firstOctet = this.readHexOctet(); |
|
7724 // Message Type Indicator |
|
7725 msg.mti = firstOctet & 0x03; |
|
7726 // User data header indicator |
|
7727 msg.udhi = firstOctet & PDU_UDHI; |
|
7728 |
|
7729 switch (msg.mti) { |
|
7730 case PDU_MTI_SMS_RESERVED: |
|
7731 // `If an MS receives a TPDU with a "Reserved" value in the TP-MTI it |
|
7732 // shall process the message as if it were an "SMS-DELIVER" but store |
|
7733 // the message exactly as received.` ~ 3GPP TS 23.040 9.2.3.1 |
|
7734 case PDU_MTI_SMS_DELIVER: |
|
7735 return this.readDeliverMessage(msg); |
|
7736 case PDU_MTI_SMS_STATUS_REPORT: |
|
7737 return this.readStatusReportMessage(msg); |
|
7738 default: |
|
7739 return null; |
|
7740 } |
|
7741 }, |
|
7742 |
|
7743 /** |
|
7744 * Helper for processing received SMS parcel data. |
|
7745 * |
|
7746 * @param length |
|
7747 * Length of SMS string in the incoming parcel. |
|
7748 * |
|
7749 * @return Message parsed or null for invalid message. |
|
7750 */ |
|
7751 processReceivedSms: function(length) { |
|
7752 if (!length) { |
|
7753 if (DEBUG) this.context.debug("Received empty SMS!"); |
|
7754 return [null, PDU_FCS_UNSPECIFIED]; |
|
7755 } |
|
7756 |
|
7757 let Buf = this.context.Buf; |
|
7758 |
|
7759 // An SMS is a string, but we won't read it as such, so let's read the |
|
7760 // string length and then defer to PDU parsing helper. |
|
7761 let messageStringLength = Buf.readInt32(); |
|
7762 if (DEBUG) this.context.debug("Got new SMS, length " + messageStringLength); |
|
7763 let message = this.readMessage(); |
|
7764 if (DEBUG) this.context.debug("Got new SMS: " + JSON.stringify(message)); |
|
7765 |
|
7766 // Read string delimiters. See Buf.readString(). |
|
7767 Buf.readStringDelimiter(length); |
|
7768 |
|
7769 // Determine result |
|
7770 if (!message) { |
|
7771 return [null, PDU_FCS_UNSPECIFIED]; |
|
7772 } |
|
7773 |
|
7774 if (message.epid == PDU_PID_SHORT_MESSAGE_TYPE_0) { |
|
7775 // `A short message type 0 indicates that the ME must acknowledge receipt |
|
7776 // of the short message but shall discard its contents.` ~ 3GPP TS 23.040 |
|
7777 // 9.2.3.9 |
|
7778 return [null, PDU_FCS_OK]; |
|
7779 } |
|
7780 |
|
7781 if (message.messageClass == GECKO_SMS_MESSAGE_CLASSES[PDU_DCS_MSG_CLASS_2]) { |
|
7782 let RIL = this.context.RIL; |
|
7783 switch (message.epid) { |
|
7784 case PDU_PID_ANSI_136_R_DATA: |
|
7785 case PDU_PID_USIM_DATA_DOWNLOAD: |
|
7786 let ICCUtilsHelper = this.context.ICCUtilsHelper; |
|
7787 if (ICCUtilsHelper.isICCServiceAvailable("DATA_DOWNLOAD_SMS_PP")) { |
|
7788 // `If the service "data download via SMS Point-to-Point" is |
|
7789 // allocated and activated in the (U)SIM Service Table, ... then the |
|
7790 // ME shall pass the message transparently to the UICC using the |
|
7791 // ENVELOPE (SMS-PP DOWNLOAD).` ~ 3GPP TS 31.111 7.1.1.1 |
|
7792 RIL.dataDownloadViaSMSPP(message); |
|
7793 |
|
7794 // `the ME shall not display the message, or alert the user of a |
|
7795 // short message waiting.` ~ 3GPP TS 31.111 7.1.1.1 |
|
7796 return [null, PDU_FCS_RESERVED]; |
|
7797 } |
|
7798 |
|
7799 // If the service "data download via SMS-PP" is not available in the |
|
7800 // (U)SIM Service Table, ..., then the ME shall store the message in |
|
7801 // EFsms in accordance with TS 31.102` ~ 3GPP TS 31.111 7.1.1.1 |
|
7802 |
|
7803 // Fall through. |
|
7804 default: |
|
7805 RIL.writeSmsToSIM(message); |
|
7806 break; |
|
7807 } |
|
7808 } |
|
7809 |
|
7810 // TODO: Bug 739143: B2G SMS: Support SMS Storage Full event |
|
7811 if ((message.messageClass != GECKO_SMS_MESSAGE_CLASSES[PDU_DCS_MSG_CLASS_0]) && !true) { |
|
7812 // `When a mobile terminated message is class 0..., the MS shall display |
|
7813 // the message immediately and send a ACK to the SC ..., irrespective of |
|
7814 // whether there is memory available in the (U)SIM or ME.` ~ 3GPP 23.038 |
|
7815 // clause 4. |
|
7816 |
|
7817 if (message.messageClass == GECKO_SMS_MESSAGE_CLASSES[PDU_DCS_MSG_CLASS_2]) { |
|
7818 // `If all the short message storage at the MS is already in use, the |
|
7819 // MS shall return "memory capacity exceeded".` ~ 3GPP 23.038 clause 4. |
|
7820 return [null, PDU_FCS_MEMORY_CAPACITY_EXCEEDED]; |
|
7821 } |
|
7822 |
|
7823 return [null, PDU_FCS_UNSPECIFIED]; |
|
7824 } |
|
7825 |
|
7826 return [message, PDU_FCS_OK]; |
|
7827 }, |
|
7828 |
|
7829 /** |
|
7830 * Read and decode a SMS-DELIVER PDU. |
|
7831 * |
|
7832 * @param msg |
|
7833 * message object for output. |
|
7834 */ |
|
7835 readDeliverMessage: function(msg) { |
|
7836 // - Sender Address info - |
|
7837 let senderAddressLength = this.readHexOctet(); |
|
7838 msg.sender = this.readAddress(senderAddressLength); |
|
7839 // - TP-Protocolo-Identifier - |
|
7840 this.readProtocolIndicator(msg); |
|
7841 // - TP-Data-Coding-Scheme - |
|
7842 this.readDataCodingScheme(msg); |
|
7843 // - TP-Service-Center-Time-Stamp - |
|
7844 msg.sentTimestamp = this.readTimestamp(); |
|
7845 // - TP-User-Data-Length - |
|
7846 let userDataLength = this.readHexOctet(); |
|
7847 |
|
7848 // - TP-User-Data - |
|
7849 if (userDataLength > 0) { |
|
7850 this.readUserData(msg, userDataLength); |
|
7851 } |
|
7852 |
|
7853 return msg; |
|
7854 }, |
|
7855 |
|
7856 /** |
|
7857 * Read and decode a SMS-STATUS-REPORT PDU. |
|
7858 * |
|
7859 * @param msg |
|
7860 * message object for output. |
|
7861 */ |
|
7862 readStatusReportMessage: function(msg) { |
|
7863 // TP-Message-Reference |
|
7864 msg.messageRef = this.readHexOctet(); |
|
7865 // TP-Recipient-Address |
|
7866 let recipientAddressLength = this.readHexOctet(); |
|
7867 msg.recipient = this.readAddress(recipientAddressLength); |
|
7868 // TP-Service-Centre-Time-Stamp |
|
7869 msg.scts = this.readTimestamp(); |
|
7870 // TP-Discharge-Time |
|
7871 msg.dt = this.readTimestamp(); |
|
7872 // TP-Status |
|
7873 msg.status = this.readHexOctet(); |
|
7874 |
|
7875 this.readExtraParams(msg); |
|
7876 |
|
7877 return msg; |
|
7878 }, |
|
7879 |
|
7880 /** |
|
7881 * Serialize a SMS-SUBMIT PDU message and write it to the output stream. |
|
7882 * |
|
7883 * This method expects that a data coding scheme has been chosen already |
|
7884 * and that the length of the user data payload in that encoding is known, |
|
7885 * too. Both go hand in hand together anyway. |
|
7886 * |
|
7887 * @param address |
|
7888 * String containing the address (number) of the SMS receiver |
|
7889 * @param userData |
|
7890 * String containing the message to be sent as user data |
|
7891 * @param dcs |
|
7892 * Data coding scheme. One of the PDU_DCS_MSG_CODING_*BITS_ALPHABET |
|
7893 * constants. |
|
7894 * @param userDataHeaderLength |
|
7895 * Length of embedded user data header, in bytes. The whole header |
|
7896 * size will be userDataHeaderLength + 1; 0 for no header. |
|
7897 * @param encodedBodyLength |
|
7898 * Length of the user data when encoded with the given DCS. For UCS2, |
|
7899 * in bytes; for 7-bit, in septets. |
|
7900 * @param langIndex |
|
7901 * Table index used for normal 7-bit encoded character lookup. |
|
7902 * @param langShiftIndex |
|
7903 * Table index used for escaped 7-bit encoded character lookup. |
|
7904 * @param requestStatusReport |
|
7905 * Request status report. |
|
7906 */ |
|
7907 writeMessage: function(options) { |
|
7908 if (DEBUG) { |
|
7909 this.context.debug("writeMessage: " + JSON.stringify(options)); |
|
7910 } |
|
7911 let Buf = this.context.Buf; |
|
7912 let address = options.number; |
|
7913 let body = options.body; |
|
7914 let dcs = options.dcs; |
|
7915 let userDataHeaderLength = options.userDataHeaderLength; |
|
7916 let encodedBodyLength = options.encodedBodyLength; |
|
7917 let langIndex = options.langIndex; |
|
7918 let langShiftIndex = options.langShiftIndex; |
|
7919 |
|
7920 // SMS-SUBMIT Format: |
|
7921 // |
|
7922 // PDU Type - 1 octet |
|
7923 // Message Reference - 1 octet |
|
7924 // DA - Destination Address - 2 to 12 octets |
|
7925 // PID - Protocol Identifier - 1 octet |
|
7926 // DCS - Data Coding Scheme - 1 octet |
|
7927 // VP - Validity Period - 0, 1 or 7 octets |
|
7928 // UDL - User Data Length - 1 octet |
|
7929 // UD - User Data - 140 octets |
|
7930 |
|
7931 let addressFormat = PDU_TOA_ISDN; // 81 |
|
7932 if (address[0] == '+') { |
|
7933 addressFormat = PDU_TOA_INTERNATIONAL | PDU_TOA_ISDN; // 91 |
|
7934 address = address.substring(1); |
|
7935 } |
|
7936 //TODO validity is unsupported for now |
|
7937 let validity = 0; |
|
7938 |
|
7939 let headerOctets = (userDataHeaderLength ? userDataHeaderLength + 1 : 0); |
|
7940 let paddingBits; |
|
7941 let userDataLengthInSeptets; |
|
7942 let userDataLengthInOctets; |
|
7943 if (dcs == PDU_DCS_MSG_CODING_7BITS_ALPHABET) { |
|
7944 let headerSeptets = Math.ceil(headerOctets * 8 / 7); |
|
7945 userDataLengthInSeptets = headerSeptets + encodedBodyLength; |
|
7946 userDataLengthInOctets = Math.ceil(userDataLengthInSeptets * 7 / 8); |
|
7947 paddingBits = headerSeptets * 7 - headerOctets * 8; |
|
7948 } else { |
|
7949 userDataLengthInOctets = headerOctets + encodedBodyLength; |
|
7950 paddingBits = 0; |
|
7951 } |
|
7952 |
|
7953 let pduOctetLength = 4 + // PDU Type, Message Ref, address length + format |
|
7954 Math.ceil(address.length / 2) + |
|
7955 3 + // PID, DCS, UDL |
|
7956 userDataLengthInOctets; |
|
7957 if (validity) { |
|
7958 //TODO: add more to pduOctetLength |
|
7959 } |
|
7960 |
|
7961 // Start the string. Since octets are represented in hex, we will need |
|
7962 // twice as many characters as octets. |
|
7963 Buf.writeInt32(pduOctetLength * 2); |
|
7964 |
|
7965 // - PDU-TYPE- |
|
7966 |
|
7967 // +--------+----------+---------+---------+--------+---------+ |
|
7968 // | RP (1) | UDHI (1) | SRR (1) | VPF (2) | RD (1) | MTI (2) | |
|
7969 // +--------+----------+---------+---------+--------+---------+ |
|
7970 // RP: 0 Reply path parameter is not set |
|
7971 // 1 Reply path parameter is set |
|
7972 // UDHI: 0 The UD Field contains only the short message |
|
7973 // 1 The beginning of the UD field contains a header in addition |
|
7974 // of the short message |
|
7975 // SRR: 0 A status report is not requested |
|
7976 // 1 A status report is requested |
|
7977 // VPF: bit4 bit3 |
|
7978 // 0 0 VP field is not present |
|
7979 // 0 1 Reserved |
|
7980 // 1 0 VP field present an integer represented (relative) |
|
7981 // 1 1 VP field present a semi-octet represented (absolute) |
|
7982 // RD: Instruct the SMSC to accept(0) or reject(1) an SMS-SUBMIT |
|
7983 // for a short message still held in the SMSC which has the same |
|
7984 // MR and DA as a previously submitted short message from the |
|
7985 // same OA |
|
7986 // MTI: bit1 bit0 Message Type |
|
7987 // 0 0 SMS-DELIVER (SMSC ==> MS) |
|
7988 // 0 1 SMS-SUBMIT (MS ==> SMSC) |
|
7989 |
|
7990 // PDU type. MTI is set to SMS-SUBMIT |
|
7991 let firstOctet = PDU_MTI_SMS_SUBMIT; |
|
7992 |
|
7993 // Status-Report-Request |
|
7994 if (options.requestStatusReport) { |
|
7995 firstOctet |= PDU_SRI_SRR; |
|
7996 } |
|
7997 |
|
7998 // Validity period |
|
7999 if (validity) { |
|
8000 //TODO: not supported yet, OR with one of PDU_VPF_* |
|
8001 } |
|
8002 // User data header indicator |
|
8003 if (headerOctets) { |
|
8004 firstOctet |= PDU_UDHI; |
|
8005 } |
|
8006 this.writeHexOctet(firstOctet); |
|
8007 |
|
8008 // Message reference 00 |
|
8009 this.writeHexOctet(0x00); |
|
8010 |
|
8011 // - Destination Address - |
|
8012 this.writeHexOctet(address.length); |
|
8013 this.writeHexOctet(addressFormat); |
|
8014 this.writeSwappedNibbleBCD(address); |
|
8015 |
|
8016 // - Protocol Identifier - |
|
8017 this.writeHexOctet(0x00); |
|
8018 |
|
8019 // - Data coding scheme - |
|
8020 // For now it assumes bits 7..4 = 1111 except for the 16 bits use case |
|
8021 this.writeHexOctet(dcs); |
|
8022 |
|
8023 // - Validity Period - |
|
8024 if (validity) { |
|
8025 this.writeHexOctet(validity); |
|
8026 } |
|
8027 |
|
8028 // - User Data - |
|
8029 if (dcs == PDU_DCS_MSG_CODING_7BITS_ALPHABET) { |
|
8030 this.writeHexOctet(userDataLengthInSeptets); |
|
8031 } else { |
|
8032 this.writeHexOctet(userDataLengthInOctets); |
|
8033 } |
|
8034 |
|
8035 if (headerOctets) { |
|
8036 this.writeUserDataHeader(options); |
|
8037 } |
|
8038 |
|
8039 switch (dcs) { |
|
8040 case PDU_DCS_MSG_CODING_7BITS_ALPHABET: |
|
8041 this.writeStringAsSeptets(body, paddingBits, langIndex, langShiftIndex); |
|
8042 break; |
|
8043 case PDU_DCS_MSG_CODING_8BITS_ALPHABET: |
|
8044 // Unsupported. |
|
8045 break; |
|
8046 case PDU_DCS_MSG_CODING_16BITS_ALPHABET: |
|
8047 this.writeUCS2String(body); |
|
8048 break; |
|
8049 } |
|
8050 |
|
8051 // End of the string. The string length is always even by definition, so |
|
8052 // we write two \0 delimiters. |
|
8053 Buf.writeUint16(0); |
|
8054 Buf.writeUint16(0); |
|
8055 }, |
|
8056 |
|
8057 /** |
|
8058 * Read GSM CBS message serial number. |
|
8059 * |
|
8060 * @param msg |
|
8061 * message object for output. |
|
8062 * |
|
8063 * @see 3GPP TS 23.041 section 9.4.1.2.1 |
|
8064 */ |
|
8065 readCbSerialNumber: function(msg) { |
|
8066 let Buf = this.context.Buf; |
|
8067 msg.serial = Buf.readUint8() << 8 | Buf.readUint8(); |
|
8068 msg.geographicalScope = (msg.serial >>> 14) & 0x03; |
|
8069 msg.messageCode = (msg.serial >>> 4) & 0x03FF; |
|
8070 msg.updateNumber = msg.serial & 0x0F; |
|
8071 }, |
|
8072 |
|
8073 /** |
|
8074 * Read GSM CBS message message identifier. |
|
8075 * |
|
8076 * @param msg |
|
8077 * message object for output. |
|
8078 * |
|
8079 * @see 3GPP TS 23.041 section 9.4.1.2.2 |
|
8080 */ |
|
8081 readCbMessageIdentifier: function(msg) { |
|
8082 let Buf = this.context.Buf; |
|
8083 msg.messageId = Buf.readUint8() << 8 | Buf.readUint8(); |
|
8084 |
|
8085 if ((msg.format != CB_FORMAT_ETWS) |
|
8086 && (msg.messageId >= CB_GSM_MESSAGEID_ETWS_BEGIN) |
|
8087 && (msg.messageId <= CB_GSM_MESSAGEID_ETWS_END)) { |
|
8088 // `In the case of transmitting CBS message for ETWS, a part of |
|
8089 // Message Code can be used to command mobile terminals to activate |
|
8090 // emergency user alert and message popup in order to alert the users.` |
|
8091 msg.etws = { |
|
8092 emergencyUserAlert: msg.messageCode & 0x0200 ? true : false, |
|
8093 popup: msg.messageCode & 0x0100 ? true : false |
|
8094 }; |
|
8095 |
|
8096 let warningType = msg.messageId - CB_GSM_MESSAGEID_ETWS_BEGIN; |
|
8097 if (warningType < CB_ETWS_WARNING_TYPE_NAMES.length) { |
|
8098 msg.etws.warningType = warningType; |
|
8099 } |
|
8100 } |
|
8101 }, |
|
8102 |
|
8103 /** |
|
8104 * Read CBS Data Coding Scheme. |
|
8105 * |
|
8106 * @param msg |
|
8107 * message object for output. |
|
8108 * |
|
8109 * @see 3GPP TS 23.038 section 5. |
|
8110 */ |
|
8111 readCbDataCodingScheme: function(msg) { |
|
8112 let dcs = this.context.Buf.readUint8(); |
|
8113 if (DEBUG) this.context.debug("PDU: read CBS dcs: " + dcs); |
|
8114 |
|
8115 let language = null, hasLanguageIndicator = false; |
|
8116 // `Any reserved codings shall be assumed to be the GSM 7bit default |
|
8117 // alphabet.` |
|
8118 let encoding = PDU_DCS_MSG_CODING_7BITS_ALPHABET; |
|
8119 let messageClass = PDU_DCS_MSG_CLASS_NORMAL; |
|
8120 |
|
8121 switch (dcs & PDU_DCS_CODING_GROUP_BITS) { |
|
8122 case 0x00: // 0000 |
|
8123 language = CB_DCS_LANG_GROUP_1[dcs & 0x0F]; |
|
8124 break; |
|
8125 |
|
8126 case 0x10: // 0001 |
|
8127 switch (dcs & 0x0F) { |
|
8128 case 0x00: |
|
8129 hasLanguageIndicator = true; |
|
8130 break; |
|
8131 case 0x01: |
|
8132 encoding = PDU_DCS_MSG_CODING_16BITS_ALPHABET; |
|
8133 hasLanguageIndicator = true; |
|
8134 break; |
|
8135 } |
|
8136 break; |
|
8137 |
|
8138 case 0x20: // 0010 |
|
8139 language = CB_DCS_LANG_GROUP_2[dcs & 0x0F]; |
|
8140 break; |
|
8141 |
|
8142 case 0x40: // 01xx |
|
8143 case 0x50: |
|
8144 //case 0x60: Text Compression, not supported |
|
8145 //case 0x70: Text Compression, not supported |
|
8146 case 0x90: // 1001 |
|
8147 encoding = (dcs & 0x0C); |
|
8148 if (encoding == 0x0C) { |
|
8149 encoding = PDU_DCS_MSG_CODING_7BITS_ALPHABET; |
|
8150 } |
|
8151 messageClass = (dcs & PDU_DCS_MSG_CLASS_BITS); |
|
8152 break; |
|
8153 |
|
8154 case 0xF0: |
|
8155 encoding = (dcs & 0x04) ? PDU_DCS_MSG_CODING_8BITS_ALPHABET |
|
8156 : PDU_DCS_MSG_CODING_7BITS_ALPHABET; |
|
8157 switch(dcs & PDU_DCS_MSG_CLASS_BITS) { |
|
8158 case 0x01: messageClass = PDU_DCS_MSG_CLASS_USER_1; break; |
|
8159 case 0x02: messageClass = PDU_DCS_MSG_CLASS_USER_2; break; |
|
8160 case 0x03: messageClass = PDU_DCS_MSG_CLASS_3; break; |
|
8161 } |
|
8162 break; |
|
8163 |
|
8164 case 0x30: // 0011 (Reserved) |
|
8165 case 0x80: // 1000 (Reserved) |
|
8166 case 0xA0: // 1010..1100 (Reserved) |
|
8167 case 0xB0: |
|
8168 case 0xC0: |
|
8169 break; |
|
8170 |
|
8171 default: |
|
8172 throw new Error("Unsupported CBS data coding scheme: " + dcs); |
|
8173 } |
|
8174 |
|
8175 msg.dcs = dcs; |
|
8176 msg.encoding = encoding; |
|
8177 msg.language = language; |
|
8178 msg.messageClass = GECKO_SMS_MESSAGE_CLASSES[messageClass]; |
|
8179 msg.hasLanguageIndicator = hasLanguageIndicator; |
|
8180 }, |
|
8181 |
|
8182 /** |
|
8183 * Read GSM CBS message page parameter. |
|
8184 * |
|
8185 * @param msg |
|
8186 * message object for output. |
|
8187 * |
|
8188 * @see 3GPP TS 23.041 section 9.4.1.2.4 |
|
8189 */ |
|
8190 readCbPageParameter: function(msg) { |
|
8191 let octet = this.context.Buf.readUint8(); |
|
8192 msg.pageIndex = (octet >>> 4) & 0x0F; |
|
8193 msg.numPages = octet & 0x0F; |
|
8194 if (!msg.pageIndex || !msg.numPages) { |
|
8195 // `If a mobile receives the code 0000 in either the first field or the |
|
8196 // second field then it shall treat the CBS message exactly the same as a |
|
8197 // CBS message with page parameter 0001 0001 (i.e. a single page message).` |
|
8198 msg.pageIndex = msg.numPages = 1; |
|
8199 } |
|
8200 }, |
|
8201 |
|
8202 /** |
|
8203 * Read ETWS Primary Notification message warning type. |
|
8204 * |
|
8205 * @param msg |
|
8206 * message object for output. |
|
8207 * |
|
8208 * @see 3GPP TS 23.041 section 9.3.24 |
|
8209 */ |
|
8210 readCbWarningType: function(msg) { |
|
8211 let Buf = this.context.Buf; |
|
8212 let word = Buf.readUint8() << 8 | Buf.readUint8(); |
|
8213 msg.etws = { |
|
8214 warningType: (word >>> 9) & 0x7F, |
|
8215 popup: word & 0x80 ? true : false, |
|
8216 emergencyUserAlert: word & 0x100 ? true : false |
|
8217 }; |
|
8218 }, |
|
8219 |
|
8220 /** |
|
8221 * Read CBS-Message-Information-Page |
|
8222 * |
|
8223 * @param msg |
|
8224 * message object for output. |
|
8225 * @param length |
|
8226 * length of cell broadcast data to read in octets. |
|
8227 * |
|
8228 * @see 3GPP TS 23.041 section 9.3.19 |
|
8229 */ |
|
8230 readGsmCbData: function(msg, length) { |
|
8231 let Buf = this.context.Buf; |
|
8232 let bufAdapter = { |
|
8233 context: this.context, |
|
8234 readHexOctet: function() { |
|
8235 return Buf.readUint8(); |
|
8236 } |
|
8237 }; |
|
8238 |
|
8239 msg.body = null; |
|
8240 msg.data = null; |
|
8241 switch (msg.encoding) { |
|
8242 case PDU_DCS_MSG_CODING_7BITS_ALPHABET: |
|
8243 msg.body = this.readSeptetsToString.call(bufAdapter, |
|
8244 (length * 8 / 7), 0, |
|
8245 PDU_NL_IDENTIFIER_DEFAULT, |
|
8246 PDU_NL_IDENTIFIER_DEFAULT); |
|
8247 if (msg.hasLanguageIndicator) { |
|
8248 msg.language = msg.body.substring(0, 2); |
|
8249 msg.body = msg.body.substring(3); |
|
8250 } |
|
8251 break; |
|
8252 |
|
8253 case PDU_DCS_MSG_CODING_8BITS_ALPHABET: |
|
8254 msg.data = Buf.readUint8Array(length); |
|
8255 break; |
|
8256 |
|
8257 case PDU_DCS_MSG_CODING_16BITS_ALPHABET: |
|
8258 if (msg.hasLanguageIndicator) { |
|
8259 msg.language = this.readSeptetsToString.call(bufAdapter, 2, 0, |
|
8260 PDU_NL_IDENTIFIER_DEFAULT, |
|
8261 PDU_NL_IDENTIFIER_DEFAULT); |
|
8262 length -= 2; |
|
8263 } |
|
8264 msg.body = this.readUCS2String.call(bufAdapter, length); |
|
8265 break; |
|
8266 } |
|
8267 }, |
|
8268 |
|
8269 /** |
|
8270 * Read Cell GSM/ETWS/UMTS Broadcast Message. |
|
8271 * |
|
8272 * @param pduLength |
|
8273 * total length of the incoming PDU in octets. |
|
8274 */ |
|
8275 readCbMessage: function(pduLength) { |
|
8276 // Validity GSM ETWS UMTS |
|
8277 let msg = { |
|
8278 // Internally used in ril_worker: |
|
8279 serial: null, // O O O |
|
8280 updateNumber: null, // O O O |
|
8281 format: null, // O O O |
|
8282 dcs: 0x0F, // O X O |
|
8283 encoding: PDU_DCS_MSG_CODING_7BITS_ALPHABET, // O X O |
|
8284 hasLanguageIndicator: false, // O X O |
|
8285 data: null, // O X O |
|
8286 body: null, // O X O |
|
8287 pageIndex: 1, // O X X |
|
8288 numPages: 1, // O X X |
|
8289 |
|
8290 // DOM attributes: |
|
8291 geographicalScope: null, // O O O |
|
8292 messageCode: null, // O O O |
|
8293 messageId: null, // O O O |
|
8294 language: null, // O X O |
|
8295 fullBody: null, // O X O |
|
8296 fullData: null, // O X O |
|
8297 messageClass: GECKO_SMS_MESSAGE_CLASSES[PDU_DCS_MSG_CLASS_NORMAL], // O x O |
|
8298 etws: null // ? O ? |
|
8299 /*{ |
|
8300 warningType: null, // X O X |
|
8301 popup: false, // X O X |
|
8302 emergencyUserAlert: false, // X O X |
|
8303 }*/ |
|
8304 }; |
|
8305 |
|
8306 if (pduLength <= CB_MESSAGE_SIZE_ETWS) { |
|
8307 msg.format = CB_FORMAT_ETWS; |
|
8308 return this.readEtwsCbMessage(msg); |
|
8309 } |
|
8310 |
|
8311 if (pduLength <= CB_MESSAGE_SIZE_GSM) { |
|
8312 msg.format = CB_FORMAT_GSM; |
|
8313 return this.readGsmCbMessage(msg, pduLength); |
|
8314 } |
|
8315 |
|
8316 return null; |
|
8317 }, |
|
8318 |
|
8319 /** |
|
8320 * Read GSM Cell Broadcast Message. |
|
8321 * |
|
8322 * @param msg |
|
8323 * message object for output. |
|
8324 * @param pduLength |
|
8325 * total length of the incomint PDU in octets. |
|
8326 * |
|
8327 * @see 3GPP TS 23.041 clause 9.4.1.2 |
|
8328 */ |
|
8329 readGsmCbMessage: function(msg, pduLength) { |
|
8330 this.readCbSerialNumber(msg); |
|
8331 this.readCbMessageIdentifier(msg); |
|
8332 this.readCbDataCodingScheme(msg); |
|
8333 this.readCbPageParameter(msg); |
|
8334 |
|
8335 // GSM CB message header takes 6 octets. |
|
8336 this.readGsmCbData(msg, pduLength - 6); |
|
8337 |
|
8338 return msg; |
|
8339 }, |
|
8340 |
|
8341 /** |
|
8342 * Read ETWS Primary Notification Message. |
|
8343 * |
|
8344 * @param msg |
|
8345 * message object for output. |
|
8346 * |
|
8347 * @see 3GPP TS 23.041 clause 9.4.1.3 |
|
8348 */ |
|
8349 readEtwsCbMessage: function(msg) { |
|
8350 this.readCbSerialNumber(msg); |
|
8351 this.readCbMessageIdentifier(msg); |
|
8352 this.readCbWarningType(msg); |
|
8353 |
|
8354 // Octet 7..56 is Warning Security Information. However, according to |
|
8355 // section 9.4.1.3.6, `The UE shall ignore this parameter.` So we just skip |
|
8356 // processing it here. |
|
8357 |
|
8358 return msg; |
|
8359 }, |
|
8360 |
|
8361 /** |
|
8362 * Read network name. |
|
8363 * |
|
8364 * @param len Length of the information element. |
|
8365 * @return |
|
8366 * { |
|
8367 * networkName: network name. |
|
8368 * shouldIncludeCi: Should Country's initials included in text string. |
|
8369 * } |
|
8370 * @see TS 24.008 clause 10.5.3.5a. |
|
8371 */ |
|
8372 readNetworkName: function(len) { |
|
8373 // According to TS 24.008 Sec. 10.5.3.5a, the first octet is: |
|
8374 // bit 8: must be 1. |
|
8375 // bit 5-7: Text encoding. |
|
8376 // 000 - GSM default alphabet. |
|
8377 // 001 - UCS2 (16 bit). |
|
8378 // else - reserved. |
|
8379 // bit 4: MS should add the letters for Country's Initials and a space |
|
8380 // to the text string if this bit is true. |
|
8381 // bit 1-3: number of spare bits in last octet. |
|
8382 |
|
8383 let codingInfo = this.readHexOctet(); |
|
8384 if (!(codingInfo & 0x80)) { |
|
8385 return null; |
|
8386 } |
|
8387 |
|
8388 let textEncoding = (codingInfo & 0x70) >> 4; |
|
8389 let shouldIncludeCountryInitials = !!(codingInfo & 0x08); |
|
8390 let spareBits = codingInfo & 0x07; |
|
8391 let resultString; |
|
8392 |
|
8393 switch (textEncoding) { |
|
8394 case 0: |
|
8395 // GSM Default alphabet. |
|
8396 resultString = this.readSeptetsToString( |
|
8397 ((len - 1) * 8 - spareBits) / 7, 0, |
|
8398 PDU_NL_IDENTIFIER_DEFAULT, |
|
8399 PDU_NL_IDENTIFIER_DEFAULT); |
|
8400 break; |
|
8401 case 1: |
|
8402 // UCS2 encoded. |
|
8403 resultString = this.readUCS2String(len - 1); |
|
8404 break; |
|
8405 default: |
|
8406 // Not an available text coding. |
|
8407 return null; |
|
8408 } |
|
8409 |
|
8410 // TODO - Bug 820286: According to shouldIncludeCountryInitials, add |
|
8411 // country initials to the resulting string. |
|
8412 return resultString; |
|
8413 } |
|
8414 }; |
|
8415 |
|
8416 /** |
|
8417 * Provide buffer with bitwise read/write function so make encoding/decoding easier. |
|
8418 */ |
|
8419 function BitBufferHelperObject(/* unused */aContext) { |
|
8420 this.readBuffer = []; |
|
8421 this.writeBuffer = []; |
|
8422 } |
|
8423 BitBufferHelperObject.prototype = { |
|
8424 readCache: 0, |
|
8425 readCacheSize: 0, |
|
8426 readBuffer: null, |
|
8427 readIndex: 0, |
|
8428 writeCache: 0, |
|
8429 writeCacheSize: 0, |
|
8430 writeBuffer: null, |
|
8431 |
|
8432 // Max length is 32 because we use integer as read/write cache. |
|
8433 // All read/write functions are implemented based on bitwise operation. |
|
8434 readBits: function(length) { |
|
8435 if (length <= 0 || length > 32) { |
|
8436 return null; |
|
8437 } |
|
8438 |
|
8439 if (length > this.readCacheSize) { |
|
8440 let bytesToRead = Math.ceil((length - this.readCacheSize) / 8); |
|
8441 for(let i = 0; i < bytesToRead; i++) { |
|
8442 this.readCache = (this.readCache << 8) | (this.readBuffer[this.readIndex++] & 0xFF); |
|
8443 this.readCacheSize += 8; |
|
8444 } |
|
8445 } |
|
8446 |
|
8447 let bitOffset = (this.readCacheSize - length), |
|
8448 resultMask = (1 << length) - 1, |
|
8449 result = 0; |
|
8450 |
|
8451 result = (this.readCache >> bitOffset) & resultMask; |
|
8452 this.readCacheSize -= length; |
|
8453 |
|
8454 return result; |
|
8455 }, |
|
8456 |
|
8457 backwardReadPilot: function(length) { |
|
8458 if (length <= 0) { |
|
8459 return; |
|
8460 } |
|
8461 |
|
8462 // Zero-based position. |
|
8463 let bitIndexToRead = this.readIndex * 8 - this.readCacheSize - length; |
|
8464 |
|
8465 if (bitIndexToRead < 0) { |
|
8466 return; |
|
8467 } |
|
8468 |
|
8469 // Update readIndex, readCache, readCacheSize accordingly. |
|
8470 let readBits = bitIndexToRead % 8; |
|
8471 this.readIndex = Math.floor(bitIndexToRead / 8) + ((readBits) ? 1 : 0); |
|
8472 this.readCache = (readBits) ? this.readBuffer[this.readIndex - 1] : 0; |
|
8473 this.readCacheSize = (readBits) ? (8 - readBits) : 0; |
|
8474 }, |
|
8475 |
|
8476 writeBits: function(value, length) { |
|
8477 if (length <= 0 || length > 32) { |
|
8478 return; |
|
8479 } |
|
8480 |
|
8481 let totalLength = length + this.writeCacheSize; |
|
8482 |
|
8483 // 8-byte cache not full |
|
8484 if (totalLength < 8) { |
|
8485 let valueMask = (1 << length) - 1; |
|
8486 this.writeCache = (this.writeCache << length) | (value & valueMask); |
|
8487 this.writeCacheSize += length; |
|
8488 return; |
|
8489 } |
|
8490 |
|
8491 // Deal with unaligned part |
|
8492 if (this.writeCacheSize) { |
|
8493 let mergeLength = 8 - this.writeCacheSize, |
|
8494 valueMask = (1 << mergeLength) - 1; |
|
8495 |
|
8496 this.writeCache = (this.writeCache << mergeLength) | ((value >> (length - mergeLength)) & valueMask); |
|
8497 this.writeBuffer.push(this.writeCache & 0xFF); |
|
8498 length -= mergeLength; |
|
8499 } |
|
8500 |
|
8501 // Aligned part, just copy |
|
8502 while (length >= 8) { |
|
8503 length -= 8; |
|
8504 this.writeBuffer.push((value >> length) & 0xFF); |
|
8505 } |
|
8506 |
|
8507 // Rest part is saved into cache |
|
8508 this.writeCacheSize = length; |
|
8509 this.writeCache = value & ((1 << length) - 1); |
|
8510 |
|
8511 return; |
|
8512 }, |
|
8513 |
|
8514 // Drop what still in read cache and goto next 8-byte alignment. |
|
8515 // There might be a better naming. |
|
8516 nextOctetAlign: function() { |
|
8517 this.readCache = 0; |
|
8518 this.readCacheSize = 0; |
|
8519 }, |
|
8520 |
|
8521 // Flush current write cache to Buf with padding 0s. |
|
8522 // There might be a better naming. |
|
8523 flushWithPadding: function() { |
|
8524 if (this.writeCacheSize) { |
|
8525 this.writeBuffer.push(this.writeCache << (8 - this.writeCacheSize)); |
|
8526 } |
|
8527 this.writeCache = 0; |
|
8528 this.writeCacheSize = 0; |
|
8529 }, |
|
8530 |
|
8531 startWrite: function(dataBuffer) { |
|
8532 this.writeBuffer = dataBuffer; |
|
8533 this.writeCache = 0; |
|
8534 this.writeCacheSize = 0; |
|
8535 }, |
|
8536 |
|
8537 startRead: function(dataBuffer) { |
|
8538 this.readBuffer = dataBuffer; |
|
8539 this.readCache = 0; |
|
8540 this.readCacheSize = 0; |
|
8541 this.readIndex = 0; |
|
8542 }, |
|
8543 |
|
8544 getWriteBufferSize: function() { |
|
8545 return this.writeBuffer.length; |
|
8546 }, |
|
8547 |
|
8548 overwriteWriteBuffer: function(position, data) { |
|
8549 let writeLength = data.length; |
|
8550 if (writeLength + position >= this.writeBuffer.length) { |
|
8551 writeLength = this.writeBuffer.length - position; |
|
8552 } |
|
8553 for (let i = 0; i < writeLength; i++) { |
|
8554 this.writeBuffer[i + position] = data[i]; |
|
8555 } |
|
8556 } |
|
8557 }; |
|
8558 |
|
8559 /** |
|
8560 * Helper for CDMA PDU |
|
8561 * |
|
8562 * Currently, some function are shared with GsmPDUHelper, they should be |
|
8563 * moved from GsmPDUHelper to a common object shared among GsmPDUHelper and |
|
8564 * CdmaPDUHelper. |
|
8565 */ |
|
8566 function CdmaPDUHelperObject(aContext) { |
|
8567 this.context = aContext; |
|
8568 } |
|
8569 CdmaPDUHelperObject.prototype = { |
|
8570 context: null, |
|
8571 |
|
8572 // 1..........C |
|
8573 // Only "1234567890*#" is defined in C.S0005-D v2.0 |
|
8574 dtmfChars: ".1234567890*#...", |
|
8575 |
|
8576 /** |
|
8577 * Entry point for SMS encoding, the options object is made compatible |
|
8578 * with existing writeMessage() of GsmPDUHelper, but less key is used. |
|
8579 * |
|
8580 * Current used key in options: |
|
8581 * @param number |
|
8582 * String containing the address (number) of the SMS receiver |
|
8583 * @param body |
|
8584 * String containing the message to be sent, segmented part |
|
8585 * @param dcs |
|
8586 * Data coding scheme. One of the PDU_DCS_MSG_CODING_*BITS_ALPHABET |
|
8587 * constants. |
|
8588 * @param encodedBodyLength |
|
8589 * Length of the user data when encoded with the given DCS. For UCS2, |
|
8590 * in bytes; for 7-bit, in septets. |
|
8591 * @param requestStatusReport |
|
8592 * Request status report. |
|
8593 * @param segmentRef |
|
8594 * Reference number of concatenated SMS message |
|
8595 * @param segmentMaxSeq |
|
8596 * Total number of concatenated SMS message |
|
8597 * @param segmentSeq |
|
8598 * Sequence number of concatenated SMS message |
|
8599 */ |
|
8600 writeMessage: function(options) { |
|
8601 if (DEBUG) { |
|
8602 this.context.debug("cdma_writeMessage: " + JSON.stringify(options)); |
|
8603 } |
|
8604 |
|
8605 // Get encoding |
|
8606 options.encoding = this.gsmDcsToCdmaEncoding(options.dcs); |
|
8607 |
|
8608 // Common Header |
|
8609 if (options.segmentMaxSeq > 1) { |
|
8610 this.writeInt(PDU_CDMA_MSG_TELESERIVCIE_ID_WEMT); |
|
8611 } else { |
|
8612 this.writeInt(PDU_CDMA_MSG_TELESERIVCIE_ID_SMS); |
|
8613 } |
|
8614 |
|
8615 this.writeInt(0); |
|
8616 this.writeInt(PDU_CDMA_MSG_CATEGORY_UNSPEC); |
|
8617 |
|
8618 // Just fill out address info in byte, rild will encap them for us |
|
8619 let addrInfo = this.encodeAddr(options.number); |
|
8620 this.writeByte(addrInfo.digitMode); |
|
8621 this.writeByte(addrInfo.numberMode); |
|
8622 this.writeByte(addrInfo.numberType); |
|
8623 this.writeByte(addrInfo.numberPlan); |
|
8624 this.writeByte(addrInfo.address.length); |
|
8625 for (let i = 0; i < addrInfo.address.length; i++) { |
|
8626 this.writeByte(addrInfo.address[i]); |
|
8627 } |
|
8628 |
|
8629 // Subaddress, not supported |
|
8630 this.writeByte(0); // Subaddress : Type |
|
8631 this.writeByte(0); // Subaddress : Odd |
|
8632 this.writeByte(0); // Subaddress : length |
|
8633 |
|
8634 // User Data |
|
8635 let encodeResult = this.encodeUserData(options); |
|
8636 this.writeByte(encodeResult.length); |
|
8637 for (let i = 0; i < encodeResult.length; i++) { |
|
8638 this.writeByte(encodeResult[i]); |
|
8639 } |
|
8640 |
|
8641 encodeResult = null; |
|
8642 }, |
|
8643 |
|
8644 /** |
|
8645 * Data writters |
|
8646 */ |
|
8647 writeInt: function(value) { |
|
8648 this.context.Buf.writeInt32(value); |
|
8649 }, |
|
8650 |
|
8651 writeByte: function(value) { |
|
8652 this.context.Buf.writeInt32(value & 0xFF); |
|
8653 }, |
|
8654 |
|
8655 /** |
|
8656 * Transform GSM DCS to CDMA encoding. |
|
8657 */ |
|
8658 gsmDcsToCdmaEncoding: function(encoding) { |
|
8659 switch (encoding) { |
|
8660 case PDU_DCS_MSG_CODING_7BITS_ALPHABET: |
|
8661 return PDU_CDMA_MSG_CODING_7BITS_ASCII; |
|
8662 case PDU_DCS_MSG_CODING_8BITS_ALPHABET: |
|
8663 return PDU_CDMA_MSG_CODING_OCTET; |
|
8664 case PDU_DCS_MSG_CODING_16BITS_ALPHABET: |
|
8665 return PDU_CDMA_MSG_CODING_UNICODE; |
|
8666 } |
|
8667 throw new Error("gsmDcsToCdmaEncoding(): Invalid GSM SMS DCS value: " + encoding); |
|
8668 }, |
|
8669 |
|
8670 /** |
|
8671 * Encode address into CDMA address format, as a byte array. |
|
8672 * |
|
8673 * @see 3GGP2 C.S0015-B 2.0, 3.4.3.3 Address Parameters |
|
8674 * |
|
8675 * @param address |
|
8676 * String of address to be encoded |
|
8677 */ |
|
8678 encodeAddr: function(address) { |
|
8679 let result = {}; |
|
8680 |
|
8681 result.numberType = PDU_CDMA_MSG_ADDR_NUMBER_TYPE_UNKNOWN; |
|
8682 result.numberPlan = PDU_CDMA_MSG_ADDR_NUMBER_TYPE_UNKNOWN; |
|
8683 |
|
8684 if (address[0] === '+') { |
|
8685 address = address.substring(1); |
|
8686 } |
|
8687 |
|
8688 // Try encode with DTMF first |
|
8689 result.digitMode = PDU_CDMA_MSG_ADDR_DIGIT_MODE_DTMF; |
|
8690 result.numberMode = PDU_CDMA_MSG_ADDR_NUMBER_MODE_ANSI; |
|
8691 |
|
8692 result.address = []; |
|
8693 for (let i = 0; i < address.length; i++) { |
|
8694 let addrDigit = this.dtmfChars.indexOf(address.charAt(i)); |
|
8695 if (addrDigit < 0) { |
|
8696 result.digitMode = PDU_CDMA_MSG_ADDR_DIGIT_MODE_ASCII; |
|
8697 result.numberMode = PDU_CDMA_MSG_ADDR_NUMBER_MODE_ASCII; |
|
8698 result.address = []; |
|
8699 break; |
|
8700 } |
|
8701 result.address.push(addrDigit); |
|
8702 } |
|
8703 |
|
8704 // Address can't be encoded with DTMF, then use 7-bit ASCII |
|
8705 if (result.digitMode !== PDU_CDMA_MSG_ADDR_DIGIT_MODE_DTMF) { |
|
8706 if (address.indexOf("@") !== -1) { |
|
8707 result.numberType = PDU_CDMA_MSG_ADDR_NUMBER_TYPE_NATIONAL; |
|
8708 } |
|
8709 |
|
8710 for (let i = 0; i < address.length; i++) { |
|
8711 result.address.push(address.charCodeAt(i) & 0x7F); |
|
8712 } |
|
8713 } |
|
8714 |
|
8715 return result; |
|
8716 }, |
|
8717 |
|
8718 /** |
|
8719 * Encode SMS contents in options into CDMA userData field. |
|
8720 * Corresponding and required subparameters will be added automatically. |
|
8721 * |
|
8722 * @see 3GGP2 C.S0015-B 2.0, 3.4.3.7 Bearer Data |
|
8723 * 4.5 Bearer Data Parameters |
|
8724 * |
|
8725 * Current used key in options: |
|
8726 * @param body |
|
8727 * String containing the message to be sent, segmented part |
|
8728 * @param encoding |
|
8729 * Encoding method of CDMA, can be transformed from GSM DCS by function |
|
8730 * cdmaPduHelp.gsmDcsToCdmaEncoding() |
|
8731 * @param encodedBodyLength |
|
8732 * Length of the user data when encoded with the given DCS. For UCS2, |
|
8733 * in bytes; for 7-bit, in septets. |
|
8734 * @param requestStatusReport |
|
8735 * Request status report. |
|
8736 * @param segmentRef |
|
8737 * Reference number of concatenated SMS message |
|
8738 * @param segmentMaxSeq |
|
8739 * Total number of concatenated SMS message |
|
8740 * @param segmentSeq |
|
8741 * Sequence number of concatenated SMS message |
|
8742 */ |
|
8743 encodeUserData: function(options) { |
|
8744 let userDataBuffer = []; |
|
8745 this.context.BitBufferHelper.startWrite(userDataBuffer); |
|
8746 |
|
8747 // Message Identifier |
|
8748 this.encodeUserDataMsgId(options); |
|
8749 |
|
8750 // User Data |
|
8751 this.encodeUserDataMsg(options); |
|
8752 |
|
8753 // Reply Option |
|
8754 this.encodeUserDataReplyOption(options); |
|
8755 |
|
8756 return userDataBuffer; |
|
8757 }, |
|
8758 |
|
8759 /** |
|
8760 * User data subparameter encoder : Message Identifier |
|
8761 * |
|
8762 * @see 3GGP2 C.S0015-B 2.0, 4.5.1 Message Identifier |
|
8763 */ |
|
8764 encodeUserDataMsgId: function(options) { |
|
8765 let BitBufferHelper = this.context.BitBufferHelper; |
|
8766 BitBufferHelper.writeBits(PDU_CDMA_MSG_USERDATA_MSG_ID, 8); |
|
8767 BitBufferHelper.writeBits(3, 8); |
|
8768 BitBufferHelper.writeBits(PDU_CDMA_MSG_TYPE_SUBMIT, 4); |
|
8769 BitBufferHelper.writeBits(1, 16); // TODO: How to get message ID? |
|
8770 if (options.segmentMaxSeq > 1) { |
|
8771 BitBufferHelper.writeBits(1, 1); |
|
8772 } else { |
|
8773 BitBufferHelper.writeBits(0, 1); |
|
8774 } |
|
8775 |
|
8776 BitBufferHelper.flushWithPadding(); |
|
8777 }, |
|
8778 |
|
8779 /** |
|
8780 * User data subparameter encoder : User Data |
|
8781 * |
|
8782 * @see 3GGP2 C.S0015-B 2.0, 4.5.2 User Data |
|
8783 */ |
|
8784 encodeUserDataMsg: function(options) { |
|
8785 let BitBufferHelper = this.context.BitBufferHelper; |
|
8786 BitBufferHelper.writeBits(PDU_CDMA_MSG_USERDATA_BODY, 8); |
|
8787 // Reserve space for length |
|
8788 BitBufferHelper.writeBits(0, 8); |
|
8789 let lengthPosition = BitBufferHelper.getWriteBufferSize(); |
|
8790 |
|
8791 BitBufferHelper.writeBits(options.encoding, 5); |
|
8792 |
|
8793 // Add user data header for message segement |
|
8794 let msgBody = options.body, |
|
8795 msgBodySize = (options.encoding === PDU_CDMA_MSG_CODING_7BITS_ASCII ? |
|
8796 options.encodedBodyLength : |
|
8797 msgBody.length); |
|
8798 if (options.segmentMaxSeq > 1) { |
|
8799 if (options.encoding === PDU_CDMA_MSG_CODING_7BITS_ASCII) { |
|
8800 BitBufferHelper.writeBits(msgBodySize + 7, 8); // Required length for user data header, in septet(7-bit) |
|
8801 |
|
8802 BitBufferHelper.writeBits(5, 8); // total header length 5 bytes |
|
8803 BitBufferHelper.writeBits(PDU_IEI_CONCATENATED_SHORT_MESSAGES_8BIT, 8); // header id 0 |
|
8804 BitBufferHelper.writeBits(3, 8); // length of element for id 0 is 3 |
|
8805 BitBufferHelper.writeBits(options.segmentRef & 0xFF, 8); // Segement reference |
|
8806 BitBufferHelper.writeBits(options.segmentMaxSeq & 0xFF, 8); // Max segment |
|
8807 BitBufferHelper.writeBits(options.segmentSeq & 0xFF, 8); // Current segment |
|
8808 BitBufferHelper.writeBits(0, 1); // Padding to make header data septet(7-bit) aligned |
|
8809 } else { |
|
8810 if (options.encoding === PDU_CDMA_MSG_CODING_UNICODE) { |
|
8811 BitBufferHelper.writeBits(msgBodySize + 3, 8); // Required length for user data header, in 16-bit |
|
8812 } else { |
|
8813 BitBufferHelper.writeBits(msgBodySize + 6, 8); // Required length for user data header, in octet(8-bit) |
|
8814 } |
|
8815 |
|
8816 BitBufferHelper.writeBits(5, 8); // total header length 5 bytes |
|
8817 BitBufferHelper.writeBits(PDU_IEI_CONCATENATED_SHORT_MESSAGES_8BIT, 8); // header id 0 |
|
8818 BitBufferHelper.writeBits(3, 8); // length of element for id 0 is 3 |
|
8819 BitBufferHelper.writeBits(options.segmentRef & 0xFF, 8); // Segement reference |
|
8820 BitBufferHelper.writeBits(options.segmentMaxSeq & 0xFF, 8); // Max segment |
|
8821 BitBufferHelper.writeBits(options.segmentSeq & 0xFF, 8); // Current segment |
|
8822 } |
|
8823 } else { |
|
8824 BitBufferHelper.writeBits(msgBodySize, 8); |
|
8825 } |
|
8826 |
|
8827 // Encode message based on encoding method |
|
8828 const langTable = PDU_NL_LOCKING_SHIFT_TABLES[PDU_NL_IDENTIFIER_DEFAULT]; |
|
8829 const langShiftTable = PDU_NL_SINGLE_SHIFT_TABLES[PDU_NL_IDENTIFIER_DEFAULT]; |
|
8830 for (let i = 0; i < msgBody.length; i++) { |
|
8831 switch (options.encoding) { |
|
8832 case PDU_CDMA_MSG_CODING_OCTET: { |
|
8833 let msgDigit = msgBody.charCodeAt(i); |
|
8834 BitBufferHelper.writeBits(msgDigit, 8); |
|
8835 break; |
|
8836 } |
|
8837 case PDU_CDMA_MSG_CODING_7BITS_ASCII: { |
|
8838 let msgDigit = msgBody.charCodeAt(i), |
|
8839 msgDigitChar = msgBody.charAt(i); |
|
8840 |
|
8841 if (msgDigit >= 32) { |
|
8842 BitBufferHelper.writeBits(msgDigit, 7); |
|
8843 } else { |
|
8844 msgDigit = langTable.indexOf(msgDigitChar); |
|
8845 |
|
8846 if (msgDigit === PDU_NL_EXTENDED_ESCAPE) { |
|
8847 break; |
|
8848 } |
|
8849 if (msgDigit >= 0) { |
|
8850 BitBufferHelper.writeBits(msgDigit, 7); |
|
8851 } else { |
|
8852 msgDigit = langShiftTable.indexOf(msgDigitChar); |
|
8853 if (msgDigit == -1) { |
|
8854 throw new Error("'" + msgDigitChar + "' is not in 7 bit alphabet " |
|
8855 + langIndex + ":" + langShiftIndex + "!"); |
|
8856 } |
|
8857 |
|
8858 if (msgDigit === PDU_NL_RESERVED_CONTROL) { |
|
8859 break; |
|
8860 } |
|
8861 |
|
8862 BitBufferHelper.writeBits(PDU_NL_EXTENDED_ESCAPE, 7); |
|
8863 BitBufferHelper.writeBits(msgDigit, 7); |
|
8864 } |
|
8865 } |
|
8866 break; |
|
8867 } |
|
8868 case PDU_CDMA_MSG_CODING_UNICODE: { |
|
8869 let msgDigit = msgBody.charCodeAt(i); |
|
8870 BitBufferHelper.writeBits(msgDigit, 16); |
|
8871 break; |
|
8872 } |
|
8873 } |
|
8874 } |
|
8875 BitBufferHelper.flushWithPadding(); |
|
8876 |
|
8877 // Fill length |
|
8878 let currentPosition = BitBufferHelper.getWriteBufferSize(); |
|
8879 BitBufferHelper.overwriteWriteBuffer(lengthPosition - 1, [currentPosition - lengthPosition]); |
|
8880 }, |
|
8881 |
|
8882 /** |
|
8883 * User data subparameter encoder : Reply Option |
|
8884 * |
|
8885 * @see 3GGP2 C.S0015-B 2.0, 4.5.11 Reply Option |
|
8886 */ |
|
8887 encodeUserDataReplyOption: function(options) { |
|
8888 if (options.requestStatusReport) { |
|
8889 let BitBufferHelper = this.context.BitBufferHelper; |
|
8890 BitBufferHelper.writeBits(PDU_CDMA_MSG_USERDATA_REPLY_OPTION, 8); |
|
8891 BitBufferHelper.writeBits(1, 8); |
|
8892 BitBufferHelper.writeBits(0, 1); // USER_ACK_REQ |
|
8893 BitBufferHelper.writeBits(1, 1); // DAK_REQ |
|
8894 BitBufferHelper.flushWithPadding(); |
|
8895 } |
|
8896 }, |
|
8897 |
|
8898 /** |
|
8899 * Entry point for SMS decoding, the returned object is made compatible |
|
8900 * with existing readMessage() of GsmPDUHelper |
|
8901 */ |
|
8902 readMessage: function() { |
|
8903 let message = {}; |
|
8904 |
|
8905 // Teleservice Identifier |
|
8906 message.teleservice = this.readInt(); |
|
8907 |
|
8908 // Message Type |
|
8909 let isServicePresent = this.readByte(); |
|
8910 if (isServicePresent) { |
|
8911 message.messageType = PDU_CDMA_MSG_TYPE_BROADCAST; |
|
8912 } else { |
|
8913 if (message.teleservice) { |
|
8914 message.messageType = PDU_CDMA_MSG_TYPE_P2P; |
|
8915 } else { |
|
8916 message.messageType = PDU_CDMA_MSG_TYPE_ACK; |
|
8917 } |
|
8918 } |
|
8919 |
|
8920 // Service Category |
|
8921 message.service = this.readInt(); |
|
8922 |
|
8923 // Originated Address |
|
8924 let addrInfo = {}; |
|
8925 addrInfo.digitMode = (this.readInt() & 0x01); |
|
8926 addrInfo.numberMode = (this.readInt() & 0x01); |
|
8927 addrInfo.numberType = (this.readInt() & 0x01); |
|
8928 addrInfo.numberPlan = (this.readInt() & 0x01); |
|
8929 addrInfo.addrLength = this.readByte(); |
|
8930 addrInfo.address = []; |
|
8931 for (let i = 0; i < addrInfo.addrLength; i++) { |
|
8932 addrInfo.address.push(this.readByte()); |
|
8933 } |
|
8934 message.sender = this.decodeAddr(addrInfo); |
|
8935 |
|
8936 // Originated Subaddress |
|
8937 addrInfo.Type = (this.readInt() & 0x07); |
|
8938 addrInfo.Odd = (this.readByte() & 0x01); |
|
8939 addrInfo.addrLength = this.readByte(); |
|
8940 for (let i = 0; i < addrInfo.addrLength; i++) { |
|
8941 let addrDigit = this.readByte(); |
|
8942 message.sender += String.fromCharCode(addrDigit); |
|
8943 } |
|
8944 |
|
8945 // Bearer Data |
|
8946 this.decodeUserData(message); |
|
8947 |
|
8948 // Bearer Data Sub-Parameter: User Data |
|
8949 let userData = message[PDU_CDMA_MSG_USERDATA_BODY]; |
|
8950 [message.header, message.body, message.encoding, message.data] = |
|
8951 (userData) ? [userData.header, userData.body, userData.encoding, userData.data] |
|
8952 : [null, null, null, null]; |
|
8953 |
|
8954 // Bearer Data Sub-Parameter: Message Status |
|
8955 // Success Delivery (0) if both Message Status and User Data are absent. |
|
8956 // Message Status absent (-1) if only User Data is available. |
|
8957 let msgStatus = message[PDU_CDMA_MSG_USER_DATA_MSG_STATUS]; |
|
8958 [message.errorClass, message.msgStatus] = |
|
8959 (msgStatus) ? [msgStatus.errorClass, msgStatus.msgStatus] |
|
8960 : ((message.body) ? [-1, -1] : [0, 0]); |
|
8961 |
|
8962 // Transform message to GSM msg |
|
8963 let msg = { |
|
8964 SMSC: "", |
|
8965 mti: 0, |
|
8966 udhi: 0, |
|
8967 sender: message.sender, |
|
8968 recipient: null, |
|
8969 pid: PDU_PID_DEFAULT, |
|
8970 epid: PDU_PID_DEFAULT, |
|
8971 dcs: 0, |
|
8972 mwi: null, |
|
8973 replace: false, |
|
8974 header: message.header, |
|
8975 body: message.body, |
|
8976 data: message.data, |
|
8977 sentTimestamp: message[PDU_CDMA_MSG_USERDATA_TIMESTAMP], |
|
8978 language: message[PDU_CDMA_LANGUAGE_INDICATOR], |
|
8979 status: null, |
|
8980 scts: null, |
|
8981 dt: null, |
|
8982 encoding: message.encoding, |
|
8983 messageClass: GECKO_SMS_MESSAGE_CLASSES[PDU_DCS_MSG_CLASS_NORMAL], |
|
8984 messageType: message.messageType, |
|
8985 serviceCategory: message.service, |
|
8986 subMsgType: message[PDU_CDMA_MSG_USERDATA_MSG_ID].msgType, |
|
8987 msgId: message[PDU_CDMA_MSG_USERDATA_MSG_ID].msgId, |
|
8988 errorClass: message.errorClass, |
|
8989 msgStatus: message.msgStatus, |
|
8990 teleservice: message.teleservice |
|
8991 }; |
|
8992 |
|
8993 return msg; |
|
8994 }, |
|
8995 |
|
8996 /** |
|
8997 * Helper for processing received SMS parcel data. |
|
8998 * |
|
8999 * @param length |
|
9000 * Length of SMS string in the incoming parcel. |
|
9001 * |
|
9002 * @return Message parsed or null for invalid message. |
|
9003 */ |
|
9004 processReceivedSms: function(length) { |
|
9005 if (!length) { |
|
9006 if (DEBUG) this.context.debug("Received empty SMS!"); |
|
9007 return [null, PDU_FCS_UNSPECIFIED]; |
|
9008 } |
|
9009 |
|
9010 let message = this.readMessage(); |
|
9011 if (DEBUG) this.context.debug("Got new SMS: " + JSON.stringify(message)); |
|
9012 |
|
9013 // Determine result |
|
9014 if (!message) { |
|
9015 return [null, PDU_FCS_UNSPECIFIED]; |
|
9016 } |
|
9017 |
|
9018 return [message, PDU_FCS_OK]; |
|
9019 }, |
|
9020 |
|
9021 /** |
|
9022 * Data readers |
|
9023 */ |
|
9024 readInt: function() { |
|
9025 return this.context.Buf.readInt32(); |
|
9026 }, |
|
9027 |
|
9028 readByte: function() { |
|
9029 return (this.context.Buf.readInt32() & 0xFF); |
|
9030 }, |
|
9031 |
|
9032 /** |
|
9033 * Decode CDMA address data into address string |
|
9034 * |
|
9035 * @see 3GGP2 C.S0015-B 2.0, 3.4.3.3 Address Parameters |
|
9036 * |
|
9037 * Required key in addrInfo |
|
9038 * @param addrLength |
|
9039 * Length of address |
|
9040 * @param digitMode |
|
9041 * Address encoding method |
|
9042 * @param address |
|
9043 * Array of encoded address data |
|
9044 */ |
|
9045 decodeAddr: function(addrInfo) { |
|
9046 let result = ""; |
|
9047 for (let i = 0; i < addrInfo.addrLength; i++) { |
|
9048 if (addrInfo.digitMode === PDU_CDMA_MSG_ADDR_DIGIT_MODE_DTMF) { |
|
9049 result += this.dtmfChars.charAt(addrInfo.address[i]); |
|
9050 } else { |
|
9051 result += String.fromCharCode(addrInfo.address[i]); |
|
9052 } |
|
9053 } |
|
9054 return result; |
|
9055 }, |
|
9056 |
|
9057 /** |
|
9058 * Read userData in parcel buffer and decode into message object. |
|
9059 * Each subparameter will be stored in corresponding key. |
|
9060 * |
|
9061 * @see 3GGP2 C.S0015-B 2.0, 3.4.3.7 Bearer Data |
|
9062 * 4.5 Bearer Data Parameters |
|
9063 */ |
|
9064 decodeUserData: function(message) { |
|
9065 let userDataLength = this.readInt(); |
|
9066 |
|
9067 while (userDataLength > 0) { |
|
9068 let id = this.readByte(), |
|
9069 length = this.readByte(), |
|
9070 userDataBuffer = []; |
|
9071 |
|
9072 for (let i = 0; i < length; i++) { |
|
9073 userDataBuffer.push(this.readByte()); |
|
9074 } |
|
9075 |
|
9076 this.context.BitBufferHelper.startRead(userDataBuffer); |
|
9077 |
|
9078 switch (id) { |
|
9079 case PDU_CDMA_MSG_USERDATA_MSG_ID: |
|
9080 message[id] = this.decodeUserDataMsgId(); |
|
9081 break; |
|
9082 case PDU_CDMA_MSG_USERDATA_BODY: |
|
9083 message[id] = this.decodeUserDataMsg(message[PDU_CDMA_MSG_USERDATA_MSG_ID].userHeader); |
|
9084 break; |
|
9085 case PDU_CDMA_MSG_USERDATA_TIMESTAMP: |
|
9086 message[id] = this.decodeUserDataTimestamp(); |
|
9087 break; |
|
9088 case PDU_CDMA_MSG_USERDATA_REPLY_OPTION: |
|
9089 message[id] = this.decodeUserDataReplyOption(); |
|
9090 break; |
|
9091 case PDU_CDMA_LANGUAGE_INDICATOR: |
|
9092 message[id] = this.decodeLanguageIndicator(); |
|
9093 break; |
|
9094 case PDU_CDMA_MSG_USERDATA_CALLBACK_NUMBER: |
|
9095 message[id] = this.decodeUserDataCallbackNumber(); |
|
9096 break; |
|
9097 case PDU_CDMA_MSG_USER_DATA_MSG_STATUS: |
|
9098 message[id] = this.decodeUserDataMsgStatus(); |
|
9099 break; |
|
9100 } |
|
9101 |
|
9102 userDataLength -= (length + 2); |
|
9103 userDataBuffer = []; |
|
9104 } |
|
9105 }, |
|
9106 |
|
9107 /** |
|
9108 * User data subparameter decoder: Message Identifier |
|
9109 * |
|
9110 * @see 3GGP2 C.S0015-B 2.0, 4.5.1 Message Identifier |
|
9111 */ |
|
9112 decodeUserDataMsgId: function() { |
|
9113 let result = {}; |
|
9114 let BitBufferHelper = this.context.BitBufferHelper; |
|
9115 result.msgType = BitBufferHelper.readBits(4); |
|
9116 result.msgId = BitBufferHelper.readBits(16); |
|
9117 result.userHeader = BitBufferHelper.readBits(1); |
|
9118 |
|
9119 return result; |
|
9120 }, |
|
9121 |
|
9122 /** |
|
9123 * Decode user data header, we only care about segment information |
|
9124 * on CDMA. |
|
9125 * |
|
9126 * This function is mostly copied from gsmPduHelper.readUserDataHeader() but |
|
9127 * change the read function, because CDMA user header decoding is't byte-wise |
|
9128 * aligned. |
|
9129 */ |
|
9130 decodeUserDataHeader: function(encoding) { |
|
9131 let BitBufferHelper = this.context.BitBufferHelper; |
|
9132 let header = {}, |
|
9133 headerSize = BitBufferHelper.readBits(8), |
|
9134 userDataHeaderSize = headerSize + 1, |
|
9135 headerPaddingBits = 0; |
|
9136 |
|
9137 // Calculate header size |
|
9138 if (encoding === PDU_DCS_MSG_CODING_7BITS_ALPHABET) { |
|
9139 // Length is in 7-bit |
|
9140 header.length = Math.ceil(userDataHeaderSize * 8 / 7); |
|
9141 // Calulate padding length |
|
9142 headerPaddingBits = (header.length * 7) - (userDataHeaderSize * 8); |
|
9143 } else if (encoding === PDU_DCS_MSG_CODING_8BITS_ALPHABET) { |
|
9144 header.length = userDataHeaderSize; |
|
9145 } else { |
|
9146 header.length = userDataHeaderSize / 2; |
|
9147 } |
|
9148 |
|
9149 while (headerSize) { |
|
9150 let identifier = BitBufferHelper.readBits(8), |
|
9151 length = BitBufferHelper.readBits(8); |
|
9152 |
|
9153 headerSize -= (2 + length); |
|
9154 |
|
9155 switch (identifier) { |
|
9156 case PDU_IEI_CONCATENATED_SHORT_MESSAGES_8BIT: { |
|
9157 let ref = BitBufferHelper.readBits(8), |
|
9158 max = BitBufferHelper.readBits(8), |
|
9159 seq = BitBufferHelper.readBits(8); |
|
9160 if (max && seq && (seq <= max)) { |
|
9161 header.segmentRef = ref; |
|
9162 header.segmentMaxSeq = max; |
|
9163 header.segmentSeq = seq; |
|
9164 } |
|
9165 break; |
|
9166 } |
|
9167 case PDU_IEI_APPLICATION_PORT_ADDRESSING_SCHEME_8BIT: { |
|
9168 let dstp = BitBufferHelper.readBits(8), |
|
9169 orip = BitBufferHelper.readBits(8); |
|
9170 if ((dstp < PDU_APA_RESERVED_8BIT_PORTS) |
|
9171 || (orip < PDU_APA_RESERVED_8BIT_PORTS)) { |
|
9172 // 3GPP TS 23.040 clause 9.2.3.24.3: "A receiving entity shall |
|
9173 // ignore any information element where the value of the |
|
9174 // Information-Element-Data is Reserved or not supported" |
|
9175 break; |
|
9176 } |
|
9177 header.destinationPort = dstp; |
|
9178 header.originatorPort = orip; |
|
9179 break; |
|
9180 } |
|
9181 case PDU_IEI_APPLICATION_PORT_ADDRESSING_SCHEME_16BIT: { |
|
9182 let dstp = (BitBufferHelper.readBits(8) << 8) | BitBufferHelper.readBits(8), |
|
9183 orip = (BitBufferHelper.readBits(8) << 8) | BitBufferHelper.readBits(8); |
|
9184 // 3GPP TS 23.040 clause 9.2.3.24.4: "A receiving entity shall |
|
9185 // ignore any information element where the value of the |
|
9186 // Information-Element-Data is Reserved or not supported" |
|
9187 if ((dstp < PDU_APA_VALID_16BIT_PORTS) |
|
9188 && (orip < PDU_APA_VALID_16BIT_PORTS)) { |
|
9189 header.destinationPort = dstp; |
|
9190 header.originatorPort = orip; |
|
9191 } |
|
9192 break; |
|
9193 } |
|
9194 case PDU_IEI_CONCATENATED_SHORT_MESSAGES_16BIT: { |
|
9195 let ref = (BitBufferHelper.readBits(8) << 8) | BitBufferHelper.readBits(8), |
|
9196 max = BitBufferHelper.readBits(8), |
|
9197 seq = BitBufferHelper.readBits(8); |
|
9198 if (max && seq && (seq <= max)) { |
|
9199 header.segmentRef = ref; |
|
9200 header.segmentMaxSeq = max; |
|
9201 header.segmentSeq = seq; |
|
9202 } |
|
9203 break; |
|
9204 } |
|
9205 case PDU_IEI_NATIONAL_LANGUAGE_SINGLE_SHIFT: { |
|
9206 let langShiftIndex = BitBufferHelper.readBits(8); |
|
9207 if (langShiftIndex < PDU_NL_SINGLE_SHIFT_TABLES.length) { |
|
9208 header.langShiftIndex = langShiftIndex; |
|
9209 } |
|
9210 break; |
|
9211 } |
|
9212 case PDU_IEI_NATIONAL_LANGUAGE_LOCKING_SHIFT: { |
|
9213 let langIndex = BitBufferHelper.readBits(8); |
|
9214 if (langIndex < PDU_NL_LOCKING_SHIFT_TABLES.length) { |
|
9215 header.langIndex = langIndex; |
|
9216 } |
|
9217 break; |
|
9218 } |
|
9219 case PDU_IEI_SPECIAL_SMS_MESSAGE_INDICATION: { |
|
9220 let msgInd = BitBufferHelper.readBits(8) & 0xFF, |
|
9221 msgCount = BitBufferHelper.readBits(8); |
|
9222 /* |
|
9223 * TS 23.040 V6.8.1 Sec 9.2.3.24.2 |
|
9224 * bits 1 0 : basic message indication type |
|
9225 * bits 4 3 2 : extended message indication type |
|
9226 * bits 6 5 : Profile id |
|
9227 * bit 7 : storage type |
|
9228 */ |
|
9229 let storeType = msgInd & PDU_MWI_STORE_TYPE_BIT; |
|
9230 header.mwi = {}; |
|
9231 mwi = header.mwi; |
|
9232 |
|
9233 if (storeType == PDU_MWI_STORE_TYPE_STORE) { |
|
9234 // Store message because TP_UDH indicates so, note this may override |
|
9235 // the setting in DCS, but that is expected |
|
9236 mwi.discard = false; |
|
9237 } else if (mwi.discard === undefined) { |
|
9238 // storeType == PDU_MWI_STORE_TYPE_DISCARD |
|
9239 // only override mwi.discard here if it hasn't already been set |
|
9240 mwi.discard = true; |
|
9241 } |
|
9242 |
|
9243 mwi.msgCount = msgCount & 0xFF; |
|
9244 mwi.active = mwi.msgCount > 0; |
|
9245 |
|
9246 if (DEBUG) { |
|
9247 this.context.debug("MWI in TP_UDH received: " + JSON.stringify(mwi)); |
|
9248 } |
|
9249 break; |
|
9250 } |
|
9251 default: |
|
9252 // Drop unsupported id |
|
9253 for (let i = 0; i < length; i++) { |
|
9254 BitBufferHelper.readBits(8); |
|
9255 } |
|
9256 } |
|
9257 } |
|
9258 |
|
9259 // Consume padding bits |
|
9260 if (headerPaddingBits) { |
|
9261 BitBufferHelper.readBits(headerPaddingBits); |
|
9262 } |
|
9263 |
|
9264 return header; |
|
9265 }, |
|
9266 |
|
9267 getCdmaMsgEncoding: function(encoding) { |
|
9268 // Determine encoding method |
|
9269 switch (encoding) { |
|
9270 case PDU_CDMA_MSG_CODING_7BITS_ASCII: |
|
9271 case PDU_CDMA_MSG_CODING_IA5: |
|
9272 case PDU_CDMA_MSG_CODING_7BITS_GSM: |
|
9273 return PDU_DCS_MSG_CODING_7BITS_ALPHABET; |
|
9274 case PDU_CDMA_MSG_CODING_OCTET: |
|
9275 case PDU_CDMA_MSG_CODING_IS_91: |
|
9276 case PDU_CDMA_MSG_CODING_LATIN_HEBREW: |
|
9277 case PDU_CDMA_MSG_CODING_LATIN: |
|
9278 return PDU_DCS_MSG_CODING_8BITS_ALPHABET; |
|
9279 case PDU_CDMA_MSG_CODING_UNICODE: |
|
9280 case PDU_CDMA_MSG_CODING_SHIFT_JIS: |
|
9281 case PDU_CDMA_MSG_CODING_KOREAN: |
|
9282 return PDU_DCS_MSG_CODING_16BITS_ALPHABET; |
|
9283 } |
|
9284 return null; |
|
9285 }, |
|
9286 |
|
9287 decodeCdmaPDUMsg: function(encoding, msgType, msgBodySize) { |
|
9288 const langTable = PDU_NL_LOCKING_SHIFT_TABLES[PDU_NL_IDENTIFIER_DEFAULT]; |
|
9289 const langShiftTable = PDU_NL_SINGLE_SHIFT_TABLES[PDU_NL_IDENTIFIER_DEFAULT]; |
|
9290 let BitBufferHelper = this.context.BitBufferHelper; |
|
9291 let result = ""; |
|
9292 let msgDigit; |
|
9293 switch (encoding) { |
|
9294 case PDU_CDMA_MSG_CODING_OCTET: // TODO : Require Test |
|
9295 while(msgBodySize > 0) { |
|
9296 msgDigit = String.fromCharCode(BitBufferHelper.readBits(8)); |
|
9297 result += msgDigit; |
|
9298 msgBodySize--; |
|
9299 } |
|
9300 break; |
|
9301 case PDU_CDMA_MSG_CODING_IS_91: // TODO : Require Test |
|
9302 // Referenced from android code |
|
9303 switch (msgType) { |
|
9304 case PDU_CDMA_MSG_CODING_IS_91_TYPE_SMS: |
|
9305 case PDU_CDMA_MSG_CODING_IS_91_TYPE_SMS_FULL: |
|
9306 case PDU_CDMA_MSG_CODING_IS_91_TYPE_VOICEMAIL_STATUS: |
|
9307 while(msgBodySize > 0) { |
|
9308 msgDigit = String.fromCharCode(BitBufferHelper.readBits(6) + 0x20); |
|
9309 result += msgDigit; |
|
9310 msgBodySize--; |
|
9311 } |
|
9312 break; |
|
9313 case PDU_CDMA_MSG_CODING_IS_91_TYPE_CLI: |
|
9314 let addrInfo = {}; |
|
9315 addrInfo.digitMode = PDU_CDMA_MSG_ADDR_DIGIT_MODE_DTMF; |
|
9316 addrInfo.numberMode = PDU_CDMA_MSG_ADDR_NUMBER_MODE_ANSI; |
|
9317 addrInfo.numberType = PDU_CDMA_MSG_ADDR_NUMBER_TYPE_UNKNOWN; |
|
9318 addrInfo.numberPlan = PDU_CDMA_MSG_ADDR_NUMBER_PLAN_UNKNOWN; |
|
9319 addrInfo.addrLength = msgBodySize; |
|
9320 addrInfo.address = []; |
|
9321 for (let i = 0; i < addrInfo.addrLength; i++) { |
|
9322 addrInfo.address.push(BitBufferHelper.readBits(4)); |
|
9323 } |
|
9324 result = this.decodeAddr(addrInfo); |
|
9325 break; |
|
9326 } |
|
9327 // Fall through. |
|
9328 case PDU_CDMA_MSG_CODING_7BITS_ASCII: |
|
9329 case PDU_CDMA_MSG_CODING_IA5: // TODO : Require Test |
|
9330 while(msgBodySize > 0) { |
|
9331 msgDigit = BitBufferHelper.readBits(7); |
|
9332 if (msgDigit >= 32) { |
|
9333 msgDigit = String.fromCharCode(msgDigit); |
|
9334 } else { |
|
9335 if (msgDigit !== PDU_NL_EXTENDED_ESCAPE) { |
|
9336 msgDigit = langTable[msgDigit]; |
|
9337 } else { |
|
9338 msgDigit = BitBufferHelper.readBits(7); |
|
9339 msgBodySize--; |
|
9340 msgDigit = langShiftTable[msgDigit]; |
|
9341 } |
|
9342 } |
|
9343 result += msgDigit; |
|
9344 msgBodySize--; |
|
9345 } |
|
9346 break; |
|
9347 case PDU_CDMA_MSG_CODING_UNICODE: |
|
9348 while(msgBodySize > 0) { |
|
9349 msgDigit = String.fromCharCode(BitBufferHelper.readBits(16)); |
|
9350 result += msgDigit; |
|
9351 msgBodySize--; |
|
9352 } |
|
9353 break; |
|
9354 case PDU_CDMA_MSG_CODING_7BITS_GSM: // TODO : Require Test |
|
9355 while(msgBodySize > 0) { |
|
9356 msgDigit = BitBufferHelper.readBits(7); |
|
9357 if (msgDigit !== PDU_NL_EXTENDED_ESCAPE) { |
|
9358 msgDigit = langTable[msgDigit]; |
|
9359 } else { |
|
9360 msgDigit = BitBufferHelper.readBits(7); |
|
9361 msgBodySize--; |
|
9362 msgDigit = langShiftTable[msgDigit]; |
|
9363 } |
|
9364 result += msgDigit; |
|
9365 msgBodySize--; |
|
9366 } |
|
9367 break; |
|
9368 case PDU_CDMA_MSG_CODING_LATIN: // TODO : Require Test |
|
9369 // Reference : http://en.wikipedia.org/wiki/ISO/IEC_8859-1 |
|
9370 while(msgBodySize > 0) { |
|
9371 msgDigit = String.fromCharCode(BitBufferHelper.readBits(8)); |
|
9372 result += msgDigit; |
|
9373 msgBodySize--; |
|
9374 } |
|
9375 break; |
|
9376 case PDU_CDMA_MSG_CODING_LATIN_HEBREW: // TODO : Require Test |
|
9377 // Reference : http://en.wikipedia.org/wiki/ISO/IEC_8859-8 |
|
9378 while(msgBodySize > 0) { |
|
9379 msgDigit = BitBufferHelper.readBits(8); |
|
9380 if (msgDigit === 0xDF) { |
|
9381 msgDigit = String.fromCharCode(0x2017); |
|
9382 } else if (msgDigit === 0xFD) { |
|
9383 msgDigit = String.fromCharCode(0x200E); |
|
9384 } else if (msgDigit === 0xFE) { |
|
9385 msgDigit = String.fromCharCode(0x200F); |
|
9386 } else if (msgDigit >= 0xE0 && msgDigit <= 0xFA) { |
|
9387 msgDigit = String.fromCharCode(0x4F0 + msgDigit); |
|
9388 } else { |
|
9389 msgDigit = String.fromCharCode(msgDigit); |
|
9390 } |
|
9391 result += msgDigit; |
|
9392 msgBodySize--; |
|
9393 } |
|
9394 break; |
|
9395 case PDU_CDMA_MSG_CODING_SHIFT_JIS: |
|
9396 // Reference : http://msdn.microsoft.com/en-US/goglobal/cc305152.aspx |
|
9397 // http://demo.icu-project.org/icu-bin/convexp?conv=Shift_JIS |
|
9398 let shift_jis_message = []; |
|
9399 |
|
9400 while (msgBodySize > 0) { |
|
9401 shift_jis_message.push(BitBufferHelper.readBits(8)); |
|
9402 msgBodySize--; |
|
9403 } |
|
9404 |
|
9405 let decoder = new TextDecoder("shift_jis"); |
|
9406 result = decoder.decode(new Uint8Array(shift_jis_message)); |
|
9407 break; |
|
9408 case PDU_CDMA_MSG_CODING_KOREAN: |
|
9409 case PDU_CDMA_MSG_CODING_GSM_DCS: |
|
9410 // Fall through. |
|
9411 default: |
|
9412 break; |
|
9413 } |
|
9414 return result; |
|
9415 }, |
|
9416 |
|
9417 /** |
|
9418 * User data subparameter decoder : User Data |
|
9419 * |
|
9420 * @see 3GGP2 C.S0015-B 2.0, 4.5.2 User Data |
|
9421 */ |
|
9422 decodeUserDataMsg: function(hasUserHeader) { |
|
9423 let BitBufferHelper = this.context.BitBufferHelper; |
|
9424 let result = {}, |
|
9425 encoding = BitBufferHelper.readBits(5), |
|
9426 msgType; |
|
9427 |
|
9428 if (encoding === PDU_CDMA_MSG_CODING_IS_91) { |
|
9429 msgType = BitBufferHelper.readBits(8); |
|
9430 } |
|
9431 result.encoding = this.getCdmaMsgEncoding(encoding); |
|
9432 |
|
9433 let msgBodySize = BitBufferHelper.readBits(8); |
|
9434 |
|
9435 // For segmented SMS, a user header is included before sms content |
|
9436 if (hasUserHeader) { |
|
9437 result.header = this.decodeUserDataHeader(result.encoding); |
|
9438 // header size is included in body size, they are decoded |
|
9439 msgBodySize -= result.header.length; |
|
9440 } |
|
9441 |
|
9442 // Store original payload if enconding is OCTET for further handling of WAP Push, etc. |
|
9443 if (encoding === PDU_CDMA_MSG_CODING_OCTET && msgBodySize > 0) { |
|
9444 result.data = new Uint8Array(msgBodySize); |
|
9445 for (let i = 0; i < msgBodySize; i++) { |
|
9446 result.data[i] = BitBufferHelper.readBits(8); |
|
9447 } |
|
9448 BitBufferHelper.backwardReadPilot(8 * msgBodySize); |
|
9449 } |
|
9450 |
|
9451 // Decode sms content |
|
9452 result.body = this.decodeCdmaPDUMsg(encoding, msgType, msgBodySize); |
|
9453 |
|
9454 return result; |
|
9455 }, |
|
9456 |
|
9457 decodeBcd: function(value) { |
|
9458 return ((value >> 4) & 0xF) * 10 + (value & 0x0F); |
|
9459 }, |
|
9460 |
|
9461 /** |
|
9462 * User data subparameter decoder : Time Stamp |
|
9463 * |
|
9464 * @see 3GGP2 C.S0015-B 2.0, 4.5.4 Message Center Time Stamp |
|
9465 */ |
|
9466 decodeUserDataTimestamp: function() { |
|
9467 let BitBufferHelper = this.context.BitBufferHelper; |
|
9468 let year = this.decodeBcd(BitBufferHelper.readBits(8)), |
|
9469 month = this.decodeBcd(BitBufferHelper.readBits(8)) - 1, |
|
9470 date = this.decodeBcd(BitBufferHelper.readBits(8)), |
|
9471 hour = this.decodeBcd(BitBufferHelper.readBits(8)), |
|
9472 min = this.decodeBcd(BitBufferHelper.readBits(8)), |
|
9473 sec = this.decodeBcd(BitBufferHelper.readBits(8)); |
|
9474 |
|
9475 if (year >= 96 && year <= 99) { |
|
9476 year += 1900; |
|
9477 } else { |
|
9478 year += 2000; |
|
9479 } |
|
9480 |
|
9481 let result = (new Date(year, month, date, hour, min, sec, 0)).valueOf(); |
|
9482 |
|
9483 return result; |
|
9484 }, |
|
9485 |
|
9486 /** |
|
9487 * User data subparameter decoder : Reply Option |
|
9488 * |
|
9489 * @see 3GGP2 C.S0015-B 2.0, 4.5.11 Reply Option |
|
9490 */ |
|
9491 decodeUserDataReplyOption: function() { |
|
9492 let replyAction = this.context.BitBufferHelper.readBits(4), |
|
9493 result = { userAck: (replyAction & 0x8) ? true : false, |
|
9494 deliverAck: (replyAction & 0x4) ? true : false, |
|
9495 readAck: (replyAction & 0x2) ? true : false, |
|
9496 report: (replyAction & 0x1) ? true : false |
|
9497 }; |
|
9498 |
|
9499 return result; |
|
9500 }, |
|
9501 |
|
9502 /** |
|
9503 * User data subparameter decoder : Language Indicator |
|
9504 * |
|
9505 * @see 3GGP2 C.S0015-B 2.0, 4.5.14 Language Indicator |
|
9506 */ |
|
9507 decodeLanguageIndicator: function() { |
|
9508 let language = this.context.BitBufferHelper.readBits(8); |
|
9509 let result = CB_CDMA_LANG_GROUP[language]; |
|
9510 return result; |
|
9511 }, |
|
9512 |
|
9513 /** |
|
9514 * User data subparameter decoder : Call-Back Number |
|
9515 * |
|
9516 * @see 3GGP2 C.S0015-B 2.0, 4.5.15 Call-Back Number |
|
9517 */ |
|
9518 decodeUserDataCallbackNumber: function() { |
|
9519 let BitBufferHelper = this.context.BitBufferHelper; |
|
9520 let digitMode = BitBufferHelper.readBits(1); |
|
9521 if (digitMode) { |
|
9522 let numberType = BitBufferHelper.readBits(3), |
|
9523 numberPlan = BitBufferHelper.readBits(4); |
|
9524 } |
|
9525 let numberFields = BitBufferHelper.readBits(8), |
|
9526 result = ""; |
|
9527 for (let i = 0; i < numberFields; i++) { |
|
9528 if (digitMode === PDU_CDMA_MSG_ADDR_DIGIT_MODE_DTMF) { |
|
9529 let addrDigit = BitBufferHelper.readBits(4); |
|
9530 result += this.dtmfChars.charAt(addrDigit); |
|
9531 } else { |
|
9532 let addrDigit = BitBufferHelper.readBits(8); |
|
9533 result += String.fromCharCode(addrDigit); |
|
9534 } |
|
9535 } |
|
9536 |
|
9537 return result; |
|
9538 }, |
|
9539 |
|
9540 /** |
|
9541 * User data subparameter decoder : Message Status |
|
9542 * |
|
9543 * @see 3GGP2 C.S0015-B 2.0, 4.5.21 Message Status |
|
9544 */ |
|
9545 decodeUserDataMsgStatus: function() { |
|
9546 let BitBufferHelper = this.context.BitBufferHelper; |
|
9547 let result = { |
|
9548 errorClass: BitBufferHelper.readBits(2), |
|
9549 msgStatus: BitBufferHelper.readBits(6) |
|
9550 }; |
|
9551 |
|
9552 return result; |
|
9553 }, |
|
9554 |
|
9555 /** |
|
9556 * Decode information record parcel. |
|
9557 */ |
|
9558 decodeInformationRecord: function() { |
|
9559 let Buf = this.context.Buf; |
|
9560 let record = {}; |
|
9561 let numOfRecords = Buf.readInt32(); |
|
9562 |
|
9563 let type; |
|
9564 for (let i = 0; i < numOfRecords; i++) { |
|
9565 type = Buf.readInt32(); |
|
9566 |
|
9567 switch (type) { |
|
9568 /* |
|
9569 * Every type is encaped by ril, except extended display |
|
9570 */ |
|
9571 case PDU_CDMA_INFO_REC_TYPE_DISPLAY: |
|
9572 record.display = Buf.readString(); |
|
9573 break; |
|
9574 case PDU_CDMA_INFO_REC_TYPE_CALLED_PARTY_NUMBER: |
|
9575 record.calledNumber = {}; |
|
9576 record.calledNumber.number = Buf.readString(); |
|
9577 record.calledNumber.type = Buf.readInt32(); |
|
9578 record.calledNumber.plan = Buf.readInt32(); |
|
9579 record.calledNumber.pi = Buf.readInt32(); |
|
9580 record.calledNumber.si = Buf.readInt32(); |
|
9581 break; |
|
9582 case PDU_CDMA_INFO_REC_TYPE_CALLING_PARTY_NUMBER: |
|
9583 record.callingNumber = {}; |
|
9584 record.callingNumber.number = Buf.readString(); |
|
9585 record.callingNumber.type = Buf.readInt32(); |
|
9586 record.callingNumber.plan = Buf.readInt32(); |
|
9587 record.callingNumber.pi = Buf.readInt32(); |
|
9588 record.callingNumber.si = Buf.readInt32(); |
|
9589 break; |
|
9590 case PDU_CDMA_INFO_REC_TYPE_CONNECTED_NUMBER: |
|
9591 record.connectedNumber = {}; |
|
9592 record.connectedNumber.number = Buf.readString(); |
|
9593 record.connectedNumber.type = Buf.readInt32(); |
|
9594 record.connectedNumber.plan = Buf.readInt32(); |
|
9595 record.connectedNumber.pi = Buf.readInt32(); |
|
9596 record.connectedNumber.si = Buf.readInt32(); |
|
9597 break; |
|
9598 case PDU_CDMA_INFO_REC_TYPE_SIGNAL: |
|
9599 record.signal = {}; |
|
9600 record.signal.present = Buf.readInt32(); |
|
9601 record.signal.type = Buf.readInt32(); |
|
9602 record.signal.alertPitch = Buf.readInt32(); |
|
9603 record.signal.signal = Buf.readInt32(); |
|
9604 break; |
|
9605 case PDU_CDMA_INFO_REC_TYPE_REDIRECTING_NUMBER: |
|
9606 record.redirect = {}; |
|
9607 record.redirect.number = Buf.readString(); |
|
9608 record.redirect.type = Buf.readInt32(); |
|
9609 record.redirect.plan = Buf.readInt32(); |
|
9610 record.redirect.pi = Buf.readInt32(); |
|
9611 record.redirect.si = Buf.readInt32(); |
|
9612 record.redirect.reason = Buf.readInt32(); |
|
9613 break; |
|
9614 case PDU_CDMA_INFO_REC_TYPE_LINE_CONTROL: |
|
9615 record.lineControl = {}; |
|
9616 record.lineControl.polarityIncluded = Buf.readInt32(); |
|
9617 record.lineControl.toggle = Buf.readInt32(); |
|
9618 record.lineControl.recerse = Buf.readInt32(); |
|
9619 record.lineControl.powerDenial = Buf.readInt32(); |
|
9620 break; |
|
9621 case PDU_CDMA_INFO_REC_TYPE_EXTENDED_DISPLAY: |
|
9622 let length = Buf.readInt32(); |
|
9623 /* |
|
9624 * Extended display is still in format defined in |
|
9625 * C.S0005-F v1.0, 3.7.5.16 |
|
9626 */ |
|
9627 record.extendedDisplay = {}; |
|
9628 |
|
9629 let headerByte = Buf.readInt32(); |
|
9630 length--; |
|
9631 // Based on spec, headerByte must be 0x80 now |
|
9632 record.extendedDisplay.indicator = (headerByte >> 7); |
|
9633 record.extendedDisplay.type = (headerByte & 0x7F); |
|
9634 record.extendedDisplay.records = []; |
|
9635 |
|
9636 while (length > 0) { |
|
9637 let display = {}; |
|
9638 |
|
9639 display.tag = Buf.readInt32(); |
|
9640 length--; |
|
9641 if (display.tag !== INFO_REC_EXTENDED_DISPLAY_BLANK && |
|
9642 display.tag !== INFO_REC_EXTENDED_DISPLAY_SKIP) { |
|
9643 display.content = Buf.readString(); |
|
9644 length -= (display.content.length + 1); |
|
9645 } |
|
9646 |
|
9647 record.extendedDisplay.records.push(display); |
|
9648 } |
|
9649 break; |
|
9650 case PDU_CDMA_INFO_REC_TYPE_T53_CLIR: |
|
9651 record.cause = Buf.readInt32(); |
|
9652 break; |
|
9653 case PDU_CDMA_INFO_REC_TYPE_T53_AUDIO_CONTROL: |
|
9654 record.audioControl = {}; |
|
9655 record.audioControl.upLink = Buf.readInt32(); |
|
9656 record.audioControl.downLink = Buf.readInt32(); |
|
9657 break; |
|
9658 case PDU_CDMA_INFO_REC_TYPE_T53_RELEASE: |
|
9659 // Fall through |
|
9660 default: |
|
9661 throw new Error("UNSOLICITED_CDMA_INFO_REC(), Unsupported information record type " + record.type + "\n"); |
|
9662 } |
|
9663 } |
|
9664 |
|
9665 return record; |
|
9666 } |
|
9667 }; |
|
9668 |
|
9669 /** |
|
9670 * Helper for processing ICC PDUs. |
|
9671 */ |
|
9672 function ICCPDUHelperObject(aContext) { |
|
9673 this.context = aContext; |
|
9674 } |
|
9675 ICCPDUHelperObject.prototype = { |
|
9676 context: null, |
|
9677 |
|
9678 /** |
|
9679 * Read GSM 8-bit unpacked octets, |
|
9680 * which are default 7-bit alphabets with bit 8 set to 0. |
|
9681 * |
|
9682 * @param numOctets |
|
9683 * Number of octets to be read. |
|
9684 */ |
|
9685 read8BitUnpackedToString: function(numOctets) { |
|
9686 let GsmPDUHelper = this.context.GsmPDUHelper; |
|
9687 |
|
9688 let ret = ""; |
|
9689 let escapeFound = false; |
|
9690 let i; |
|
9691 const langTable = PDU_NL_LOCKING_SHIFT_TABLES[PDU_NL_IDENTIFIER_DEFAULT]; |
|
9692 const langShiftTable = PDU_NL_SINGLE_SHIFT_TABLES[PDU_NL_IDENTIFIER_DEFAULT]; |
|
9693 |
|
9694 for(i = 0; i < numOctets; i++) { |
|
9695 let octet = GsmPDUHelper.readHexOctet(); |
|
9696 if (octet == 0xff) { |
|
9697 i++; |
|
9698 break; |
|
9699 } |
|
9700 |
|
9701 if (escapeFound) { |
|
9702 escapeFound = false; |
|
9703 if (octet == PDU_NL_EXTENDED_ESCAPE) { |
|
9704 // According to 3GPP TS 23.038, section 6.2.1.1, NOTE 1, "On |
|
9705 // receipt of this code, a receiving entity shall display a space |
|
9706 // until another extensiion table is defined." |
|
9707 ret += " "; |
|
9708 } else if (octet == PDU_NL_RESERVED_CONTROL) { |
|
9709 // According to 3GPP TS 23.038 B.2, "This code represents a control |
|
9710 // character and therefore must not be used for language specific |
|
9711 // characters." |
|
9712 ret += " "; |
|
9713 } else { |
|
9714 ret += langShiftTable[octet]; |
|
9715 } |
|
9716 } else if (octet == PDU_NL_EXTENDED_ESCAPE) { |
|
9717 escapeFound = true; |
|
9718 } else { |
|
9719 ret += langTable[octet]; |
|
9720 } |
|
9721 } |
|
9722 |
|
9723 let Buf = this.context.Buf; |
|
9724 Buf.seekIncoming((numOctets - i) * Buf.PDU_HEX_OCTET_SIZE); |
|
9725 return ret; |
|
9726 }, |
|
9727 |
|
9728 /** |
|
9729 * Write GSM 8-bit unpacked octets. |
|
9730 * |
|
9731 * @param numOctets Number of total octets to be writen, including trailing |
|
9732 * 0xff. |
|
9733 * @param str String to be written. Could be null. |
|
9734 */ |
|
9735 writeStringTo8BitUnpacked: function(numOctets, str) { |
|
9736 const langTable = PDU_NL_LOCKING_SHIFT_TABLES[PDU_NL_IDENTIFIER_DEFAULT]; |
|
9737 const langShiftTable = PDU_NL_SINGLE_SHIFT_TABLES[PDU_NL_IDENTIFIER_DEFAULT]; |
|
9738 |
|
9739 let GsmPDUHelper = this.context.GsmPDUHelper; |
|
9740 |
|
9741 // If the character is GSM extended alphabet, two octets will be written. |
|
9742 // So we need to keep track of number of octets to be written. |
|
9743 let i, j; |
|
9744 let len = str ? str.length : 0; |
|
9745 for (i = 0, j = 0; i < len && j < numOctets; i++) { |
|
9746 let c = str.charAt(i); |
|
9747 let octet = langTable.indexOf(c); |
|
9748 |
|
9749 if (octet == -1) { |
|
9750 // Make sure we still have enough space to write two octets. |
|
9751 if (j + 2 > numOctets) { |
|
9752 break; |
|
9753 } |
|
9754 |
|
9755 octet = langShiftTable.indexOf(c); |
|
9756 if (octet == -1) { |
|
9757 // Fallback to ASCII space. |
|
9758 octet = langTable.indexOf(' '); |
|
9759 } |
|
9760 GsmPDUHelper.writeHexOctet(PDU_NL_EXTENDED_ESCAPE); |
|
9761 j++; |
|
9762 } |
|
9763 GsmPDUHelper.writeHexOctet(octet); |
|
9764 j++; |
|
9765 } |
|
9766 |
|
9767 // trailing 0xff |
|
9768 while (j++ < numOctets) { |
|
9769 GsmPDUHelper.writeHexOctet(0xff); |
|
9770 } |
|
9771 }, |
|
9772 |
|
9773 /** |
|
9774 * Read UCS2 String on UICC. |
|
9775 * |
|
9776 * @see TS 101.221, Annex A. |
|
9777 * @param scheme |
|
9778 * Coding scheme for UCS2 on UICC. One of 0x80, 0x81 or 0x82. |
|
9779 * @param numOctets |
|
9780 * Number of octets to be read as UCS2 string. |
|
9781 */ |
|
9782 readICCUCS2String: function(scheme, numOctets) { |
|
9783 let Buf = this.context.Buf; |
|
9784 let GsmPDUHelper = this.context.GsmPDUHelper; |
|
9785 |
|
9786 let str = ""; |
|
9787 switch (scheme) { |
|
9788 /** |
|
9789 * +------+---------+---------+---------+---------+------+------+ |
|
9790 * | 0x80 | Ch1_msb | Ch1_lsb | Ch2_msb | Ch2_lsb | 0xff | 0xff | |
|
9791 * +------+---------+---------+---------+---------+------+------+ |
|
9792 */ |
|
9793 case 0x80: |
|
9794 let isOdd = numOctets % 2; |
|
9795 let i; |
|
9796 for (i = 0; i < numOctets - isOdd; i += 2) { |
|
9797 let code = (GsmPDUHelper.readHexOctet() << 8) | GsmPDUHelper.readHexOctet(); |
|
9798 if (code == 0xffff) { |
|
9799 i += 2; |
|
9800 break; |
|
9801 } |
|
9802 str += String.fromCharCode(code); |
|
9803 } |
|
9804 |
|
9805 // Skip trailing 0xff |
|
9806 Buf.seekIncoming((numOctets - i) * Buf.PDU_HEX_OCTET_SIZE); |
|
9807 break; |
|
9808 case 0x81: // Fall through |
|
9809 case 0x82: |
|
9810 /** |
|
9811 * +------+-----+--------+-----+-----+-----+--------+------+ |
|
9812 * | 0x81 | len | offset | Ch1 | Ch2 | ... | Ch_len | 0xff | |
|
9813 * +------+-----+--------+-----+-----+-----+--------+------+ |
|
9814 * |
|
9815 * len : The length of characters. |
|
9816 * offset : 0hhh hhhh h000 0000 |
|
9817 * Ch_n: bit 8 = 0 |
|
9818 * GSM default alphabets |
|
9819 * bit 8 = 1 |
|
9820 * UCS2 character whose char code is (Ch_n & 0x7f) + offset |
|
9821 * |
|
9822 * +------+-----+------------+------------+-----+-----+-----+--------+ |
|
9823 * | 0x82 | len | offset_msb | offset_lsb | Ch1 | Ch2 | ... | Ch_len | |
|
9824 * +------+-----+------------+------------+-----+-----+-----+--------+ |
|
9825 * |
|
9826 * len : The length of characters. |
|
9827 * offset_msb, offset_lsn: offset |
|
9828 * Ch_n: bit 8 = 0 |
|
9829 * GSM default alphabets |
|
9830 * bit 8 = 1 |
|
9831 * UCS2 character whose char code is (Ch_n & 0x7f) + offset |
|
9832 */ |
|
9833 let len = GsmPDUHelper.readHexOctet(); |
|
9834 let offset, headerLen; |
|
9835 if (scheme == 0x81) { |
|
9836 offset = GsmPDUHelper.readHexOctet() << 7; |
|
9837 headerLen = 2; |
|
9838 } else { |
|
9839 offset = (GsmPDUHelper.readHexOctet() << 8) | GsmPDUHelper.readHexOctet(); |
|
9840 headerLen = 3; |
|
9841 } |
|
9842 |
|
9843 for (let i = 0; i < len; i++) { |
|
9844 let ch = GsmPDUHelper.readHexOctet(); |
|
9845 if (ch & 0x80) { |
|
9846 // UCS2 |
|
9847 str += String.fromCharCode((ch & 0x7f) + offset); |
|
9848 } else { |
|
9849 // GSM 8bit |
|
9850 let count = 0, gotUCS2 = 0; |
|
9851 while ((i + count + 1 < len)) { |
|
9852 count++; |
|
9853 if (GsmPDUHelper.readHexOctet() & 0x80) { |
|
9854 gotUCS2 = 1; |
|
9855 break; |
|
9856 } |
|
9857 } |
|
9858 // Unread. |
|
9859 // +1 for the GSM alphabet indexed at i, |
|
9860 Buf.seekIncoming(-1 * (count + 1) * Buf.PDU_HEX_OCTET_SIZE); |
|
9861 str += this.read8BitUnpackedToString(count + 1 - gotUCS2); |
|
9862 i += count - gotUCS2; |
|
9863 } |
|
9864 } |
|
9865 |
|
9866 // Skipping trailing 0xff |
|
9867 Buf.seekIncoming((numOctets - len - headerLen) * Buf.PDU_HEX_OCTET_SIZE); |
|
9868 break; |
|
9869 } |
|
9870 return str; |
|
9871 }, |
|
9872 |
|
9873 /** |
|
9874 * Read Alpha Id and Dialling number from TS TS 151.011 clause 10.5.1 |
|
9875 * |
|
9876 * @param recordSize The size of linear fixed record. |
|
9877 */ |
|
9878 readAlphaIdDiallingNumber: function(recordSize) { |
|
9879 let Buf = this.context.Buf; |
|
9880 let length = Buf.readInt32(); |
|
9881 |
|
9882 let alphaLen = recordSize - ADN_FOOTER_SIZE_BYTES; |
|
9883 let alphaId = this.readAlphaIdentifier(alphaLen); |
|
9884 |
|
9885 let number = this.readNumberWithLength(); |
|
9886 |
|
9887 // Skip 2 unused octets, CCP and EXT1. |
|
9888 Buf.seekIncoming(2 * Buf.PDU_HEX_OCTET_SIZE); |
|
9889 Buf.readStringDelimiter(length); |
|
9890 |
|
9891 let contact = null; |
|
9892 if (alphaId || number) { |
|
9893 contact = {alphaId: alphaId, |
|
9894 number: number}; |
|
9895 } |
|
9896 return contact; |
|
9897 }, |
|
9898 |
|
9899 /** |
|
9900 * Write Alpha Identifier and Dialling number from TS 151.011 clause 10.5.1 |
|
9901 * |
|
9902 * @param recordSize The size of linear fixed record. |
|
9903 * @param alphaId Alpha Identifier to be written. |
|
9904 * @param number Dialling Number to be written. |
|
9905 */ |
|
9906 writeAlphaIdDiallingNumber: function(recordSize, alphaId, number) { |
|
9907 let Buf = this.context.Buf; |
|
9908 let GsmPDUHelper = this.context.GsmPDUHelper; |
|
9909 |
|
9910 // Write String length |
|
9911 let strLen = recordSize * 2; |
|
9912 Buf.writeInt32(strLen); |
|
9913 |
|
9914 let alphaLen = recordSize - ADN_FOOTER_SIZE_BYTES; |
|
9915 this.writeAlphaIdentifier(alphaLen, alphaId); |
|
9916 this.writeNumberWithLength(number); |
|
9917 |
|
9918 // Write unused octets 0xff, CCP and EXT1. |
|
9919 GsmPDUHelper.writeHexOctet(0xff); |
|
9920 GsmPDUHelper.writeHexOctet(0xff); |
|
9921 Buf.writeStringDelimiter(strLen); |
|
9922 }, |
|
9923 |
|
9924 /** |
|
9925 * Read Alpha Identifier. |
|
9926 * |
|
9927 * @see TS 131.102 |
|
9928 * |
|
9929 * @param numOctets |
|
9930 * Number of octets to be read. |
|
9931 * |
|
9932 * It uses either |
|
9933 * 1. SMS default 7-bit alphabet with bit 8 set to 0. |
|
9934 * 2. UCS2 string. |
|
9935 * |
|
9936 * Unused bytes should be set to 0xff. |
|
9937 */ |
|
9938 readAlphaIdentifier: function(numOctets) { |
|
9939 if (numOctets === 0) { |
|
9940 return ""; |
|
9941 } |
|
9942 |
|
9943 let temp; |
|
9944 // Read the 1st octet to determine the encoding. |
|
9945 if ((temp = this.context.GsmPDUHelper.readHexOctet()) == 0x80 || |
|
9946 temp == 0x81 || |
|
9947 temp == 0x82) { |
|
9948 numOctets--; |
|
9949 return this.readICCUCS2String(temp, numOctets); |
|
9950 } else { |
|
9951 let Buf = this.context.Buf; |
|
9952 Buf.seekIncoming(-1 * Buf.PDU_HEX_OCTET_SIZE); |
|
9953 return this.read8BitUnpackedToString(numOctets); |
|
9954 } |
|
9955 }, |
|
9956 |
|
9957 /** |
|
9958 * Write Alpha Identifier. |
|
9959 * |
|
9960 * @param numOctets |
|
9961 * Total number of octets to be written. This includes the length of |
|
9962 * alphaId and the length of trailing unused octets(0xff). |
|
9963 * @param alphaId |
|
9964 * Alpha Identifier to be written. |
|
9965 * |
|
9966 * Unused octets will be written as 0xff. |
|
9967 */ |
|
9968 writeAlphaIdentifier: function(numOctets, alphaId) { |
|
9969 if (numOctets === 0) { |
|
9970 return; |
|
9971 } |
|
9972 |
|
9973 // If alphaId is empty or it's of GSM 8 bit. |
|
9974 if (!alphaId || this.context.ICCUtilsHelper.isGsm8BitAlphabet(alphaId)) { |
|
9975 this.writeStringTo8BitUnpacked(numOctets, alphaId); |
|
9976 } else { |
|
9977 let GsmPDUHelper = this.context.GsmPDUHelper; |
|
9978 |
|
9979 // Currently only support UCS2 coding scheme 0x80. |
|
9980 GsmPDUHelper.writeHexOctet(0x80); |
|
9981 numOctets--; |
|
9982 // Now the alphaId is UCS2 string, each character will take 2 octets. |
|
9983 if (alphaId.length * 2 > numOctets) { |
|
9984 alphaId = alphaId.substring(0, Math.floor(numOctets / 2)); |
|
9985 } |
|
9986 GsmPDUHelper.writeUCS2String(alphaId); |
|
9987 for (let i = alphaId.length * 2; i < numOctets; i++) { |
|
9988 GsmPDUHelper.writeHexOctet(0xff); |
|
9989 } |
|
9990 } |
|
9991 }, |
|
9992 |
|
9993 /** |
|
9994 * Read Dialling number. |
|
9995 * |
|
9996 * @see TS 131.102 |
|
9997 * |
|
9998 * @param len |
|
9999 * The Length of BCD number. |
|
10000 * |
|
10001 * From TS 131.102, in EF_ADN, EF_FDN, the field 'Length of BCD number' |
|
10002 * means the total bytes should be allocated to store the TON/NPI and |
|
10003 * the dialing number. |
|
10004 * For example, if the dialing number is 1234567890, |
|
10005 * and the TON/NPI is 0x81, |
|
10006 * The field 'Length of BCD number' should be 06, which is |
|
10007 * 1 byte to store the TON/NPI, 0x81 |
|
10008 * 5 bytes to store the BCD number 2143658709. |
|
10009 * |
|
10010 * Here the definition of the length is different from SMS spec, |
|
10011 * TS 23.040 9.1.2.5, which the length means |
|
10012 * "number of useful semi-octets within the Address-Value field". |
|
10013 */ |
|
10014 readDiallingNumber: function(len) { |
|
10015 if (DEBUG) this.context.debug("PDU: Going to read Dialling number: " + len); |
|
10016 if (len === 0) { |
|
10017 return ""; |
|
10018 } |
|
10019 |
|
10020 let GsmPDUHelper = this.context.GsmPDUHelper; |
|
10021 |
|
10022 // TOA = TON + NPI |
|
10023 let toa = GsmPDUHelper.readHexOctet(); |
|
10024 |
|
10025 let number = GsmPDUHelper.readSwappedNibbleBcdString(len - 1); |
|
10026 if (number.length <= 0) { |
|
10027 if (DEBUG) this.context.debug("No number provided"); |
|
10028 return ""; |
|
10029 } |
|
10030 if ((toa >> 4) == (PDU_TOA_INTERNATIONAL >> 4)) { |
|
10031 number = '+' + number; |
|
10032 } |
|
10033 return number; |
|
10034 }, |
|
10035 |
|
10036 /** |
|
10037 * Write Dialling Number. |
|
10038 * |
|
10039 * @param number The Dialling number |
|
10040 */ |
|
10041 writeDiallingNumber: function(number) { |
|
10042 let GsmPDUHelper = this.context.GsmPDUHelper; |
|
10043 |
|
10044 let toa = PDU_TOA_ISDN; // 81 |
|
10045 if (number[0] == '+') { |
|
10046 toa = PDU_TOA_INTERNATIONAL | PDU_TOA_ISDN; // 91 |
|
10047 number = number.substring(1); |
|
10048 } |
|
10049 GsmPDUHelper.writeHexOctet(toa); |
|
10050 GsmPDUHelper.writeSwappedNibbleBCD(number); |
|
10051 }, |
|
10052 |
|
10053 readNumberWithLength: function() { |
|
10054 let Buf = this.context.Buf; |
|
10055 let number; |
|
10056 let numLen = this.context.GsmPDUHelper.readHexOctet(); |
|
10057 if (numLen != 0xff) { |
|
10058 if (numLen > ADN_MAX_BCD_NUMBER_BYTES) { |
|
10059 throw new Error("invalid length of BCD number/SSC contents - " + numLen); |
|
10060 } |
|
10061 |
|
10062 number = this.readDiallingNumber(numLen); |
|
10063 Buf.seekIncoming((ADN_MAX_BCD_NUMBER_BYTES - numLen) * Buf.PDU_HEX_OCTET_SIZE); |
|
10064 } else { |
|
10065 Buf.seekIncoming(ADN_MAX_BCD_NUMBER_BYTES * Buf.PDU_HEX_OCTET_SIZE); |
|
10066 } |
|
10067 |
|
10068 return number; |
|
10069 }, |
|
10070 |
|
10071 writeNumberWithLength: function(number) { |
|
10072 let GsmPDUHelper = this.context.GsmPDUHelper; |
|
10073 |
|
10074 if (number) { |
|
10075 let numStart = number[0] == "+" ? 1 : 0; |
|
10076 number = number.substring(0, numStart) + |
|
10077 number.substring(numStart) |
|
10078 .replace(/[^0-9*#,]/g, "") |
|
10079 .replace(/\*/g, "a") |
|
10080 .replace(/\#/g, "b") |
|
10081 .replace(/\,/g, "c"); |
|
10082 |
|
10083 let numDigits = number.length - numStart; |
|
10084 if (numDigits > ADN_MAX_NUMBER_DIGITS) { |
|
10085 number = number.substring(0, ADN_MAX_NUMBER_DIGITS + numStart); |
|
10086 numDigits = number.length - numStart; |
|
10087 } |
|
10088 |
|
10089 // +1 for TON/NPI |
|
10090 let numLen = Math.ceil(numDigits / 2) + 1; |
|
10091 GsmPDUHelper.writeHexOctet(numLen); |
|
10092 this.writeDiallingNumber(number); |
|
10093 // Write trailing 0xff of Dialling Number. |
|
10094 for (let i = 0; i < ADN_MAX_BCD_NUMBER_BYTES - numLen; i++) { |
|
10095 GsmPDUHelper.writeHexOctet(0xff); |
|
10096 } |
|
10097 } else { |
|
10098 // +1 for numLen |
|
10099 for (let i = 0; i < ADN_MAX_BCD_NUMBER_BYTES + 1; i++) { |
|
10100 GsmPDUHelper.writeHexOctet(0xff); |
|
10101 } |
|
10102 } |
|
10103 } |
|
10104 }; |
|
10105 |
|
10106 function StkCommandParamsFactoryObject(aContext) { |
|
10107 this.context = aContext; |
|
10108 } |
|
10109 StkCommandParamsFactoryObject.prototype = { |
|
10110 context: null, |
|
10111 |
|
10112 createParam: function(cmdDetails, ctlvs) { |
|
10113 let method = this[cmdDetails.typeOfCommand]; |
|
10114 if (typeof method != "function") { |
|
10115 if (DEBUG) { |
|
10116 this.context.debug("Unknown proactive command " + |
|
10117 cmdDetails.typeOfCommand.toString(16)); |
|
10118 } |
|
10119 return null; |
|
10120 } |
|
10121 return method.call(this, cmdDetails, ctlvs); |
|
10122 }, |
|
10123 |
|
10124 /** |
|
10125 * Construct a param for Refresh. |
|
10126 * |
|
10127 * @param cmdDetails |
|
10128 * The value object of CommandDetails TLV. |
|
10129 * @param ctlvs |
|
10130 * The all TLVs in this proactive command. |
|
10131 */ |
|
10132 processRefresh: function(cmdDetails, ctlvs) { |
|
10133 let refreshType = cmdDetails.commandQualifier; |
|
10134 switch (refreshType) { |
|
10135 case STK_REFRESH_FILE_CHANGE: |
|
10136 case STK_REFRESH_NAA_INIT_AND_FILE_CHANGE: |
|
10137 let ctlv = this.context.StkProactiveCmdHelper.searchForTag( |
|
10138 COMPREHENSIONTLV_TAG_FILE_LIST, ctlvs); |
|
10139 if (ctlv) { |
|
10140 let list = ctlv.value.fileList; |
|
10141 if (DEBUG) { |
|
10142 this.context.debug("Refresh, list = " + list); |
|
10143 } |
|
10144 this.context.ICCRecordHelper.fetchICCRecords(); |
|
10145 } |
|
10146 break; |
|
10147 } |
|
10148 return null; |
|
10149 }, |
|
10150 |
|
10151 /** |
|
10152 * Construct a param for Poll Interval. |
|
10153 * |
|
10154 * @param cmdDetails |
|
10155 * The value object of CommandDetails TLV. |
|
10156 * @param ctlvs |
|
10157 * The all TLVs in this proactive command. |
|
10158 */ |
|
10159 processPollInterval: function(cmdDetails, ctlvs) { |
|
10160 let ctlv = this.context.StkProactiveCmdHelper.searchForTag( |
|
10161 COMPREHENSIONTLV_TAG_DURATION, ctlvs); |
|
10162 if (!ctlv) { |
|
10163 this.context.RIL.sendStkTerminalResponse({ |
|
10164 command: cmdDetails, |
|
10165 resultCode: STK_RESULT_REQUIRED_VALUES_MISSING}); |
|
10166 throw new Error("Stk Poll Interval: Required value missing : Duration"); |
|
10167 } |
|
10168 |
|
10169 return ctlv.value; |
|
10170 }, |
|
10171 |
|
10172 /** |
|
10173 * Construct a param for Poll Off. |
|
10174 * |
|
10175 * @param cmdDetails |
|
10176 * The value object of CommandDetails TLV. |
|
10177 * @param ctlvs |
|
10178 * The all TLVs in this proactive command. |
|
10179 */ |
|
10180 processPollOff: function(cmdDetails, ctlvs) { |
|
10181 return null; |
|
10182 }, |
|
10183 |
|
10184 /** |
|
10185 * Construct a param for Set Up Event list. |
|
10186 * |
|
10187 * @param cmdDetails |
|
10188 * The value object of CommandDetails TLV. |
|
10189 * @param ctlvs |
|
10190 * The all TLVs in this proactive command. |
|
10191 */ |
|
10192 processSetUpEventList: function(cmdDetails, ctlvs) { |
|
10193 let ctlv = this.context.StkProactiveCmdHelper.searchForTag( |
|
10194 COMPREHENSIONTLV_TAG_EVENT_LIST, ctlvs); |
|
10195 if (!ctlv) { |
|
10196 this.context.RIL.sendStkTerminalResponse({ |
|
10197 command: cmdDetails, |
|
10198 resultCode: STK_RESULT_REQUIRED_VALUES_MISSING}); |
|
10199 throw new Error("Stk Event List: Required value missing : Event List"); |
|
10200 } |
|
10201 |
|
10202 return ctlv.value || {eventList: null}; |
|
10203 }, |
|
10204 |
|
10205 /** |
|
10206 * Construct a param for Select Item. |
|
10207 * |
|
10208 * @param cmdDetails |
|
10209 * The value object of CommandDetails TLV. |
|
10210 * @param ctlvs |
|
10211 * The all TLVs in this proactive command. |
|
10212 */ |
|
10213 processSelectItem: function(cmdDetails, ctlvs) { |
|
10214 let StkProactiveCmdHelper = this.context.StkProactiveCmdHelper; |
|
10215 let menu = {}; |
|
10216 |
|
10217 let ctlv = StkProactiveCmdHelper.searchForTag(COMPREHENSIONTLV_TAG_ALPHA_ID, ctlvs); |
|
10218 if (ctlv) { |
|
10219 menu.title = ctlv.value.identifier; |
|
10220 } |
|
10221 |
|
10222 menu.items = []; |
|
10223 for (let i = 0; i < ctlvs.length; i++) { |
|
10224 let ctlv = ctlvs[i]; |
|
10225 if (ctlv.tag == COMPREHENSIONTLV_TAG_ITEM) { |
|
10226 menu.items.push(ctlv.value); |
|
10227 } |
|
10228 } |
|
10229 |
|
10230 if (menu.items.length === 0) { |
|
10231 this.context.RIL.sendStkTerminalResponse({ |
|
10232 command: cmdDetails, |
|
10233 resultCode: STK_RESULT_REQUIRED_VALUES_MISSING}); |
|
10234 throw new Error("Stk Menu: Required value missing : items"); |
|
10235 } |
|
10236 |
|
10237 ctlv = StkProactiveCmdHelper.searchForTag(COMPREHENSIONTLV_TAG_ITEM_ID, ctlvs); |
|
10238 if (ctlv) { |
|
10239 menu.defaultItem = ctlv.value.identifier - 1; |
|
10240 } |
|
10241 |
|
10242 // The 1st bit and 2nd bit determines the presentation type. |
|
10243 menu.presentationType = cmdDetails.commandQualifier & 0x03; |
|
10244 |
|
10245 // Help information available. |
|
10246 if (cmdDetails.commandQualifier & 0x80) { |
|
10247 menu.isHelpAvailable = true; |
|
10248 } |
|
10249 |
|
10250 ctlv = StkProactiveCmdHelper.searchForTag(COMPREHENSIONTLV_TAG_NEXT_ACTION_IND, ctlvs); |
|
10251 if (ctlv) { |
|
10252 menu.nextActionList = ctlv.value; |
|
10253 } |
|
10254 |
|
10255 return menu; |
|
10256 }, |
|
10257 |
|
10258 processDisplayText: function(cmdDetails, ctlvs) { |
|
10259 let StkProactiveCmdHelper = this.context.StkProactiveCmdHelper; |
|
10260 let textMsg = {}; |
|
10261 |
|
10262 let ctlv = StkProactiveCmdHelper.searchForTag(COMPREHENSIONTLV_TAG_TEXT_STRING, ctlvs); |
|
10263 if (!ctlv) { |
|
10264 this.context.RIL.sendStkTerminalResponse({ |
|
10265 command: cmdDetails, |
|
10266 resultCode: STK_RESULT_REQUIRED_VALUES_MISSING}); |
|
10267 throw new Error("Stk Display Text: Required value missing : Text String"); |
|
10268 } |
|
10269 textMsg.text = ctlv.value.textString; |
|
10270 |
|
10271 ctlv = StkProactiveCmdHelper.searchForTag(COMPREHENSIONTLV_TAG_IMMEDIATE_RESPONSE, ctlvs); |
|
10272 if (ctlv) { |
|
10273 textMsg.responseNeeded = true; |
|
10274 } |
|
10275 |
|
10276 ctlv = StkProactiveCmdHelper.searchForTag(COMPREHENSIONTLV_TAG_DURATION, ctlvs); |
|
10277 if (ctlv) { |
|
10278 textMsg.duration = ctlv.value; |
|
10279 } |
|
10280 |
|
10281 // High priority. |
|
10282 if (cmdDetails.commandQualifier & 0x01) { |
|
10283 textMsg.isHighPriority = true; |
|
10284 } |
|
10285 |
|
10286 // User clear. |
|
10287 if (cmdDetails.commandQualifier & 0x80) { |
|
10288 textMsg.userClear = true; |
|
10289 } |
|
10290 |
|
10291 return textMsg; |
|
10292 }, |
|
10293 |
|
10294 processSetUpIdleModeText: function(cmdDetails, ctlvs) { |
|
10295 let textMsg = {}; |
|
10296 |
|
10297 let ctlv = this.context.StkProactiveCmdHelper.searchForTag( |
|
10298 COMPREHENSIONTLV_TAG_TEXT_STRING, ctlvs); |
|
10299 if (!ctlv) { |
|
10300 this.context.RIL.sendStkTerminalResponse({ |
|
10301 command: cmdDetails, |
|
10302 resultCode: STK_RESULT_REQUIRED_VALUES_MISSING}); |
|
10303 throw new Error("Stk Set Up Idle Text: Required value missing : Text String"); |
|
10304 } |
|
10305 textMsg.text = ctlv.value.textString; |
|
10306 |
|
10307 return textMsg; |
|
10308 }, |
|
10309 |
|
10310 processGetInkey: function(cmdDetails, ctlvs) { |
|
10311 let StkProactiveCmdHelper = this.context.StkProactiveCmdHelper; |
|
10312 let input = {}; |
|
10313 |
|
10314 let ctlv = StkProactiveCmdHelper.searchForTag(COMPREHENSIONTLV_TAG_TEXT_STRING, ctlvs); |
|
10315 if (!ctlv) { |
|
10316 this.context.RIL.sendStkTerminalResponse({ |
|
10317 command: cmdDetails, |
|
10318 resultCode: STK_RESULT_REQUIRED_VALUES_MISSING}); |
|
10319 throw new Error("Stk Get InKey: Required value missing : Text String"); |
|
10320 } |
|
10321 input.text = ctlv.value.textString; |
|
10322 |
|
10323 // duration |
|
10324 ctlv = StkProactiveCmdHelper.searchForTag( |
|
10325 COMPREHENSIONTLV_TAG_DURATION, ctlvs); |
|
10326 if (ctlv) { |
|
10327 input.duration = ctlv.value; |
|
10328 } |
|
10329 |
|
10330 input.minLength = 1; |
|
10331 input.maxLength = 1; |
|
10332 |
|
10333 // isAlphabet |
|
10334 if (cmdDetails.commandQualifier & 0x01) { |
|
10335 input.isAlphabet = true; |
|
10336 } |
|
10337 |
|
10338 // UCS2 |
|
10339 if (cmdDetails.commandQualifier & 0x02) { |
|
10340 input.isUCS2 = true; |
|
10341 } |
|
10342 |
|
10343 // Character sets defined in bit 1 and bit 2 are disable and |
|
10344 // the YES/NO reponse is required. |
|
10345 if (cmdDetails.commandQualifier & 0x04) { |
|
10346 input.isYesNoRequested = true; |
|
10347 } |
|
10348 |
|
10349 // Help information available. |
|
10350 if (cmdDetails.commandQualifier & 0x80) { |
|
10351 input.isHelpAvailable = true; |
|
10352 } |
|
10353 |
|
10354 return input; |
|
10355 }, |
|
10356 |
|
10357 processGetInput: function(cmdDetails, ctlvs) { |
|
10358 let StkProactiveCmdHelper = this.context.StkProactiveCmdHelper; |
|
10359 let input = {}; |
|
10360 |
|
10361 let ctlv = StkProactiveCmdHelper.searchForTag(COMPREHENSIONTLV_TAG_TEXT_STRING, ctlvs); |
|
10362 if (!ctlv) { |
|
10363 this.context.RIL.sendStkTerminalResponse({ |
|
10364 command: cmdDetails, |
|
10365 resultCode: STK_RESULT_REQUIRED_VALUES_MISSING}); |
|
10366 throw new Error("Stk Get Input: Required value missing : Text String"); |
|
10367 } |
|
10368 input.text = ctlv.value.textString; |
|
10369 |
|
10370 ctlv = StkProactiveCmdHelper.searchForTag(COMPREHENSIONTLV_TAG_RESPONSE_LENGTH, ctlvs); |
|
10371 if (ctlv) { |
|
10372 input.minLength = ctlv.value.minLength; |
|
10373 input.maxLength = ctlv.value.maxLength; |
|
10374 } |
|
10375 |
|
10376 ctlv = StkProactiveCmdHelper.searchForTag(COMPREHENSIONTLV_TAG_DEFAULT_TEXT, ctlvs); |
|
10377 if (ctlv) { |
|
10378 input.defaultText = ctlv.value.textString; |
|
10379 } |
|
10380 |
|
10381 // Alphabet only |
|
10382 if (cmdDetails.commandQualifier & 0x01) { |
|
10383 input.isAlphabet = true; |
|
10384 } |
|
10385 |
|
10386 // UCS2 |
|
10387 if (cmdDetails.commandQualifier & 0x02) { |
|
10388 input.isUCS2 = true; |
|
10389 } |
|
10390 |
|
10391 // User input shall not be revealed |
|
10392 if (cmdDetails.commandQualifier & 0x04) { |
|
10393 input.hideInput = true; |
|
10394 } |
|
10395 |
|
10396 // User input in SMS packed format |
|
10397 if (cmdDetails.commandQualifier & 0x08) { |
|
10398 input.isPacked = true; |
|
10399 } |
|
10400 |
|
10401 // Help information available. |
|
10402 if (cmdDetails.commandQualifier & 0x80) { |
|
10403 input.isHelpAvailable = true; |
|
10404 } |
|
10405 |
|
10406 return input; |
|
10407 }, |
|
10408 |
|
10409 processEventNotify: function(cmdDetails, ctlvs) { |
|
10410 let textMsg = {}; |
|
10411 |
|
10412 let ctlv = this.context.StkProactiveCmdHelper.searchForTag( |
|
10413 COMPREHENSIONTLV_TAG_ALPHA_ID, ctlvs); |
|
10414 if (!ctlv) { |
|
10415 this.context.RIL.sendStkTerminalResponse({ |
|
10416 command: cmdDetails, |
|
10417 resultCode: STK_RESULT_REQUIRED_VALUES_MISSING}); |
|
10418 throw new Error("Stk Event Notfiy: Required value missing : Alpha ID"); |
|
10419 } |
|
10420 textMsg.text = ctlv.value.identifier; |
|
10421 |
|
10422 return textMsg; |
|
10423 }, |
|
10424 |
|
10425 processSetupCall: function(cmdDetails, ctlvs) { |
|
10426 let StkProactiveCmdHelper = this.context.StkProactiveCmdHelper; |
|
10427 let call = {}; |
|
10428 let iter = Iterator(ctlvs); |
|
10429 |
|
10430 let ctlv = StkProactiveCmdHelper.searchForNextTag(COMPREHENSIONTLV_TAG_ALPHA_ID, iter); |
|
10431 if (ctlv) { |
|
10432 call.confirmMessage = ctlv.value.identifier; |
|
10433 } |
|
10434 |
|
10435 ctlv = StkProactiveCmdHelper.searchForNextTag(COMPREHENSIONTLV_TAG_ALPHA_ID, iter); |
|
10436 if (ctlv) { |
|
10437 call.callMessage = ctlv.value.identifier; |
|
10438 } |
|
10439 |
|
10440 ctlv = StkProactiveCmdHelper.searchForTag(COMPREHENSIONTLV_TAG_ADDRESS, ctlvs); |
|
10441 if (!ctlv) { |
|
10442 this.context.RIL.sendStkTerminalResponse({ |
|
10443 command: cmdDetails, |
|
10444 resultCode: STK_RESULT_REQUIRED_VALUES_MISSING}); |
|
10445 throw new Error("Stk Set Up Call: Required value missing : Adress"); |
|
10446 } |
|
10447 call.address = ctlv.value.number; |
|
10448 |
|
10449 // see 3GPP TS 31.111 section 6.4.13 |
|
10450 ctlv = StkProactiveCmdHelper.searchForTag(COMPREHENSIONTLV_TAG_DURATION, ctlvs); |
|
10451 if (ctlv) { |
|
10452 call.duration = ctlv.value; |
|
10453 } |
|
10454 |
|
10455 return call; |
|
10456 }, |
|
10457 |
|
10458 processLaunchBrowser: function(cmdDetails, ctlvs) { |
|
10459 let StkProactiveCmdHelper = this.context.StkProactiveCmdHelper; |
|
10460 let browser = {}; |
|
10461 |
|
10462 let ctlv = StkProactiveCmdHelper.searchForTag(COMPREHENSIONTLV_TAG_URL, ctlvs); |
|
10463 if (!ctlv) { |
|
10464 this.context.RIL.sendStkTerminalResponse({ |
|
10465 command: cmdDetails, |
|
10466 resultCode: STK_RESULT_REQUIRED_VALUES_MISSING}); |
|
10467 throw new Error("Stk Launch Browser: Required value missing : URL"); |
|
10468 } |
|
10469 browser.url = ctlv.value.url; |
|
10470 |
|
10471 ctlv = StkProactiveCmdHelper.searchForTag(COMPREHENSIONTLV_TAG_ALPHA_ID, ctlvs); |
|
10472 if (ctlv) { |
|
10473 browser.confirmMessage = ctlv.value.identifier; |
|
10474 } |
|
10475 |
|
10476 browser.mode = cmdDetails.commandQualifier & 0x03; |
|
10477 |
|
10478 return browser; |
|
10479 }, |
|
10480 |
|
10481 processPlayTone: function(cmdDetails, ctlvs) { |
|
10482 let StkProactiveCmdHelper = this.context.StkProactiveCmdHelper; |
|
10483 let playTone = {}; |
|
10484 |
|
10485 let ctlv = StkProactiveCmdHelper.searchForTag( |
|
10486 COMPREHENSIONTLV_TAG_ALPHA_ID, ctlvs); |
|
10487 if (ctlv) { |
|
10488 playTone.text = ctlv.value.identifier; |
|
10489 } |
|
10490 |
|
10491 ctlv = StkProactiveCmdHelper.searchForTag(COMPREHENSIONTLV_TAG_TONE, ctlvs); |
|
10492 if (ctlv) { |
|
10493 playTone.tone = ctlv.value.tone; |
|
10494 } |
|
10495 |
|
10496 ctlv = StkProactiveCmdHelper.searchForTag( |
|
10497 COMPREHENSIONTLV_TAG_DURATION, ctlvs); |
|
10498 if (ctlv) { |
|
10499 playTone.duration = ctlv.value; |
|
10500 } |
|
10501 |
|
10502 // vibrate is only defined in TS 102.223 |
|
10503 playTone.isVibrate = (cmdDetails.commandQualifier & 0x01) !== 0x00; |
|
10504 |
|
10505 return playTone; |
|
10506 }, |
|
10507 |
|
10508 /** |
|
10509 * Construct a param for Provide Local Information |
|
10510 * |
|
10511 * @param cmdDetails |
|
10512 * The value object of CommandDetails TLV. |
|
10513 * @param ctlvs |
|
10514 * The all TLVs in this proactive command. |
|
10515 */ |
|
10516 processProvideLocalInfo: function(cmdDetails, ctlvs) { |
|
10517 let provideLocalInfo = { |
|
10518 localInfoType: cmdDetails.commandQualifier |
|
10519 }; |
|
10520 return provideLocalInfo; |
|
10521 }, |
|
10522 |
|
10523 processTimerManagement: function(cmdDetails, ctlvs) { |
|
10524 let StkProactiveCmdHelper = this.context.StkProactiveCmdHelper; |
|
10525 let timer = { |
|
10526 timerAction: cmdDetails.commandQualifier |
|
10527 }; |
|
10528 |
|
10529 let ctlv = StkProactiveCmdHelper.searchForTag( |
|
10530 COMPREHENSIONTLV_TAG_TIMER_IDENTIFIER, ctlvs); |
|
10531 if (ctlv) { |
|
10532 timer.timerId = ctlv.value.timerId; |
|
10533 } |
|
10534 |
|
10535 ctlv = StkProactiveCmdHelper.searchForTag( |
|
10536 COMPREHENSIONTLV_TAG_TIMER_VALUE, ctlvs); |
|
10537 if (ctlv) { |
|
10538 timer.timerValue = ctlv.value.timerValue; |
|
10539 } |
|
10540 |
|
10541 return timer; |
|
10542 }, |
|
10543 |
|
10544 /** |
|
10545 * Construct a param for BIP commands. |
|
10546 * |
|
10547 * @param cmdDetails |
|
10548 * The value object of CommandDetails TLV. |
|
10549 * @param ctlvs |
|
10550 * The all TLVs in this proactive command. |
|
10551 */ |
|
10552 processBipMessage: function(cmdDetails, ctlvs) { |
|
10553 let bipMsg = {}; |
|
10554 |
|
10555 let ctlv = this.context.StkProactiveCmdHelper.searchForTag( |
|
10556 COMPREHENSIONTLV_TAG_ALPHA_ID, ctlvs); |
|
10557 if (ctlv) { |
|
10558 bipMsg.text = ctlv.value.identifier; |
|
10559 } |
|
10560 |
|
10561 return bipMsg; |
|
10562 } |
|
10563 }; |
|
10564 StkCommandParamsFactoryObject.prototype[STK_CMD_REFRESH] = function STK_CMD_REFRESH(cmdDetails, ctlvs) { |
|
10565 return this.processRefresh(cmdDetails, ctlvs); |
|
10566 }; |
|
10567 StkCommandParamsFactoryObject.prototype[STK_CMD_POLL_INTERVAL] = function STK_CMD_POLL_INTERVAL(cmdDetails, ctlvs) { |
|
10568 return this.processPollInterval(cmdDetails, ctlvs); |
|
10569 }; |
|
10570 StkCommandParamsFactoryObject.prototype[STK_CMD_POLL_OFF] = function STK_CMD_POLL_OFF(cmdDetails, ctlvs) { |
|
10571 return this.processPollOff(cmdDetails, ctlvs); |
|
10572 }; |
|
10573 StkCommandParamsFactoryObject.prototype[STK_CMD_PROVIDE_LOCAL_INFO] = function STK_CMD_PROVIDE_LOCAL_INFO(cmdDetails, ctlvs) { |
|
10574 return this.processProvideLocalInfo(cmdDetails, ctlvs); |
|
10575 }; |
|
10576 StkCommandParamsFactoryObject.prototype[STK_CMD_SET_UP_EVENT_LIST] = function STK_CMD_SET_UP_EVENT_LIST(cmdDetails, ctlvs) { |
|
10577 return this.processSetUpEventList(cmdDetails, ctlvs); |
|
10578 }; |
|
10579 StkCommandParamsFactoryObject.prototype[STK_CMD_SET_UP_MENU] = function STK_CMD_SET_UP_MENU(cmdDetails, ctlvs) { |
|
10580 return this.processSelectItem(cmdDetails, ctlvs); |
|
10581 }; |
|
10582 StkCommandParamsFactoryObject.prototype[STK_CMD_SELECT_ITEM] = function STK_CMD_SELECT_ITEM(cmdDetails, ctlvs) { |
|
10583 return this.processSelectItem(cmdDetails, ctlvs); |
|
10584 }; |
|
10585 StkCommandParamsFactoryObject.prototype[STK_CMD_DISPLAY_TEXT] = function STK_CMD_DISPLAY_TEXT(cmdDetails, ctlvs) { |
|
10586 return this.processDisplayText(cmdDetails, ctlvs); |
|
10587 }; |
|
10588 StkCommandParamsFactoryObject.prototype[STK_CMD_SET_UP_IDLE_MODE_TEXT] = function STK_CMD_SET_UP_IDLE_MODE_TEXT(cmdDetails, ctlvs) { |
|
10589 return this.processSetUpIdleModeText(cmdDetails, ctlvs); |
|
10590 }; |
|
10591 StkCommandParamsFactoryObject.prototype[STK_CMD_GET_INKEY] = function STK_CMD_GET_INKEY(cmdDetails, ctlvs) { |
|
10592 return this.processGetInkey(cmdDetails, ctlvs); |
|
10593 }; |
|
10594 StkCommandParamsFactoryObject.prototype[STK_CMD_GET_INPUT] = function STK_CMD_GET_INPUT(cmdDetails, ctlvs) { |
|
10595 return this.processGetInput(cmdDetails, ctlvs); |
|
10596 }; |
|
10597 StkCommandParamsFactoryObject.prototype[STK_CMD_SEND_SS] = function STK_CMD_SEND_SS(cmdDetails, ctlvs) { |
|
10598 return this.processEventNotify(cmdDetails, ctlvs); |
|
10599 }; |
|
10600 StkCommandParamsFactoryObject.prototype[STK_CMD_SEND_USSD] = function STK_CMD_SEND_USSD(cmdDetails, ctlvs) { |
|
10601 return this.processEventNotify(cmdDetails, ctlvs); |
|
10602 }; |
|
10603 StkCommandParamsFactoryObject.prototype[STK_CMD_SEND_SMS] = function STK_CMD_SEND_SMS(cmdDetails, ctlvs) { |
|
10604 return this.processEventNotify(cmdDetails, ctlvs); |
|
10605 }; |
|
10606 StkCommandParamsFactoryObject.prototype[STK_CMD_SEND_DTMF] = function STK_CMD_SEND_DTMF(cmdDetails, ctlvs) { |
|
10607 return this.processEventNotify(cmdDetails, ctlvs); |
|
10608 }; |
|
10609 StkCommandParamsFactoryObject.prototype[STK_CMD_SET_UP_CALL] = function STK_CMD_SET_UP_CALL(cmdDetails, ctlvs) { |
|
10610 return this.processSetupCall(cmdDetails, ctlvs); |
|
10611 }; |
|
10612 StkCommandParamsFactoryObject.prototype[STK_CMD_LAUNCH_BROWSER] = function STK_CMD_LAUNCH_BROWSER(cmdDetails, ctlvs) { |
|
10613 return this.processLaunchBrowser(cmdDetails, ctlvs); |
|
10614 }; |
|
10615 StkCommandParamsFactoryObject.prototype[STK_CMD_PLAY_TONE] = function STK_CMD_PLAY_TONE(cmdDetails, ctlvs) { |
|
10616 return this.processPlayTone(cmdDetails, ctlvs); |
|
10617 }; |
|
10618 StkCommandParamsFactoryObject.prototype[STK_CMD_TIMER_MANAGEMENT] = function STK_CMD_TIMER_MANAGEMENT(cmdDetails, ctlvs) { |
|
10619 return this.processTimerManagement(cmdDetails, ctlvs); |
|
10620 }; |
|
10621 StkCommandParamsFactoryObject.prototype[STK_CMD_OPEN_CHANNEL] = function STK_CMD_OPEN_CHANNEL(cmdDetails, ctlvs) { |
|
10622 return this.processBipMessage(cmdDetails, ctlvs); |
|
10623 }; |
|
10624 StkCommandParamsFactoryObject.prototype[STK_CMD_CLOSE_CHANNEL] = function STK_CMD_CLOSE_CHANNEL(cmdDetails, ctlvs) { |
|
10625 return this.processBipMessage(cmdDetails, ctlvs); |
|
10626 }; |
|
10627 StkCommandParamsFactoryObject.prototype[STK_CMD_RECEIVE_DATA] = function STK_CMD_RECEIVE_DATA(cmdDetails, ctlvs) { |
|
10628 return this.processBipMessage(cmdDetails, ctlvs); |
|
10629 }; |
|
10630 StkCommandParamsFactoryObject.prototype[STK_CMD_SEND_DATA] = function STK_CMD_SEND_DATA(cmdDetails, ctlvs) { |
|
10631 return this.processBipMessage(cmdDetails, ctlvs); |
|
10632 }; |
|
10633 |
|
10634 function StkProactiveCmdHelperObject(aContext) { |
|
10635 this.context = aContext; |
|
10636 } |
|
10637 StkProactiveCmdHelperObject.prototype = { |
|
10638 context: null, |
|
10639 |
|
10640 retrieve: function(tag, length) { |
|
10641 let method = this[tag]; |
|
10642 if (typeof method != "function") { |
|
10643 if (DEBUG) { |
|
10644 this.context.debug("Unknown comprehension tag " + tag.toString(16)); |
|
10645 } |
|
10646 let Buf = this.context.Buf; |
|
10647 Buf.seekIncoming(length * Buf.PDU_HEX_OCTET_SIZE); |
|
10648 return null; |
|
10649 } |
|
10650 return method.call(this, length); |
|
10651 }, |
|
10652 |
|
10653 /** |
|
10654 * Command Details. |
|
10655 * |
|
10656 * | Byte | Description | Length | |
|
10657 * | 1 | Command details Tag | 1 | |
|
10658 * | 2 | Length = 03 | 1 | |
|
10659 * | 3 | Command number | 1 | |
|
10660 * | 4 | Type of Command | 1 | |
|
10661 * | 5 | Command Qualifier | 1 | |
|
10662 */ |
|
10663 retrieveCommandDetails: function(length) { |
|
10664 let GsmPDUHelper = this.context.GsmPDUHelper; |
|
10665 let cmdDetails = { |
|
10666 commandNumber: GsmPDUHelper.readHexOctet(), |
|
10667 typeOfCommand: GsmPDUHelper.readHexOctet(), |
|
10668 commandQualifier: GsmPDUHelper.readHexOctet() |
|
10669 }; |
|
10670 return cmdDetails; |
|
10671 }, |
|
10672 |
|
10673 /** |
|
10674 * Device Identities. |
|
10675 * |
|
10676 * | Byte | Description | Length | |
|
10677 * | 1 | Device Identity Tag | 1 | |
|
10678 * | 2 | Length = 02 | 1 | |
|
10679 * | 3 | Source device Identity | 1 | |
|
10680 * | 4 | Destination device Id | 1 | |
|
10681 */ |
|
10682 retrieveDeviceId: function(length) { |
|
10683 let GsmPDUHelper = this.context.GsmPDUHelper; |
|
10684 let deviceId = { |
|
10685 sourceId: GsmPDUHelper.readHexOctet(), |
|
10686 destinationId: GsmPDUHelper.readHexOctet() |
|
10687 }; |
|
10688 return deviceId; |
|
10689 }, |
|
10690 |
|
10691 /** |
|
10692 * Alpha Identifier. |
|
10693 * |
|
10694 * | Byte | Description | Length | |
|
10695 * | 1 | Alpha Identifier Tag | 1 | |
|
10696 * | 2 ~ (Y-1)+2 | Length (X) | Y | |
|
10697 * | (Y-1)+3 ~ | Alpha identfier | X | |
|
10698 * | (Y-1)+X+2 | | | |
|
10699 */ |
|
10700 retrieveAlphaId: function(length) { |
|
10701 let alphaId = { |
|
10702 identifier: this.context.ICCPDUHelper.readAlphaIdentifier(length) |
|
10703 }; |
|
10704 return alphaId; |
|
10705 }, |
|
10706 |
|
10707 /** |
|
10708 * Duration. |
|
10709 * |
|
10710 * | Byte | Description | Length | |
|
10711 * | 1 | Response Length Tag | 1 | |
|
10712 * | 2 | Lenth = 02 | 1 | |
|
10713 * | 3 | Time unit | 1 | |
|
10714 * | 4 | Time interval | 1 | |
|
10715 */ |
|
10716 retrieveDuration: function(length) { |
|
10717 let GsmPDUHelper = this.context.GsmPDUHelper; |
|
10718 let duration = { |
|
10719 timeUnit: GsmPDUHelper.readHexOctet(), |
|
10720 timeInterval: GsmPDUHelper.readHexOctet(), |
|
10721 }; |
|
10722 return duration; |
|
10723 }, |
|
10724 |
|
10725 /** |
|
10726 * Address. |
|
10727 * |
|
10728 * | Byte | Description | Length | |
|
10729 * | 1 | Alpha Identifier Tag | 1 | |
|
10730 * | 2 ~ (Y-1)+2 | Length (X) | Y | |
|
10731 * | (Y-1)+3 | TON and NPI | 1 | |
|
10732 * | (Y-1)+4 ~ | Dialling number | X | |
|
10733 * | (Y-1)+X+2 | | | |
|
10734 */ |
|
10735 retrieveAddress: function(length) { |
|
10736 let address = { |
|
10737 number : this.context.ICCPDUHelper.readDiallingNumber(length) |
|
10738 }; |
|
10739 return address; |
|
10740 }, |
|
10741 |
|
10742 /** |
|
10743 * Text String. |
|
10744 * |
|
10745 * | Byte | Description | Length | |
|
10746 * | 1 | Text String Tag | 1 | |
|
10747 * | 2 ~ (Y-1)+2 | Length (X) | Y | |
|
10748 * | (Y-1)+3 | Data coding scheme | 1 | |
|
10749 * | (Y-1)+4~ | Text String | X | |
|
10750 * | (Y-1)+X+2 | | | |
|
10751 */ |
|
10752 retrieveTextString: function(length) { |
|
10753 if (!length) { |
|
10754 // null string. |
|
10755 return {textString: null}; |
|
10756 } |
|
10757 |
|
10758 let GsmPDUHelper = this.context.GsmPDUHelper; |
|
10759 let text = { |
|
10760 codingScheme: GsmPDUHelper.readHexOctet() |
|
10761 }; |
|
10762 |
|
10763 length--; // -1 for the codingScheme. |
|
10764 switch (text.codingScheme & 0x0f) { |
|
10765 case STK_TEXT_CODING_GSM_7BIT_PACKED: |
|
10766 text.textString = GsmPDUHelper.readSeptetsToString(length * 8 / 7, 0, 0, 0); |
|
10767 break; |
|
10768 case STK_TEXT_CODING_GSM_8BIT: |
|
10769 text.textString = |
|
10770 this.context.ICCPDUHelper.read8BitUnpackedToString(length); |
|
10771 break; |
|
10772 case STK_TEXT_CODING_UCS2: |
|
10773 text.textString = GsmPDUHelper.readUCS2String(length); |
|
10774 break; |
|
10775 } |
|
10776 return text; |
|
10777 }, |
|
10778 |
|
10779 /** |
|
10780 * Tone. |
|
10781 * |
|
10782 * | Byte | Description | Length | |
|
10783 * | 1 | Tone Tag | 1 | |
|
10784 * | 2 | Lenth = 01 | 1 | |
|
10785 * | 3 | Tone | 1 | |
|
10786 */ |
|
10787 retrieveTone: function(length) { |
|
10788 let tone = { |
|
10789 tone: this.context.GsmPDUHelper.readHexOctet(), |
|
10790 }; |
|
10791 return tone; |
|
10792 }, |
|
10793 |
|
10794 /** |
|
10795 * Item. |
|
10796 * |
|
10797 * | Byte | Description | Length | |
|
10798 * | 1 | Item Tag | 1 | |
|
10799 * | 2 ~ (Y-1)+2 | Length (X) | Y | |
|
10800 * | (Y-1)+3 | Identifier of item | 1 | |
|
10801 * | (Y-1)+4 ~ | Text string of item | X | |
|
10802 * | (Y-1)+X+2 | | | |
|
10803 */ |
|
10804 retrieveItem: function(length) { |
|
10805 // TS 102.223 ,clause 6.6.7 SET-UP MENU |
|
10806 // If the "Item data object for item 1" is a null data object |
|
10807 // (i.e. length = '00' and no value part), this is an indication to the ME |
|
10808 // to remove the existing menu from the menu system in the ME. |
|
10809 if (!length) { |
|
10810 return null; |
|
10811 } |
|
10812 let item = { |
|
10813 identifier: this.context.GsmPDUHelper.readHexOctet(), |
|
10814 text: this.context.ICCPDUHelper.readAlphaIdentifier(length - 1) |
|
10815 }; |
|
10816 return item; |
|
10817 }, |
|
10818 |
|
10819 /** |
|
10820 * Item Identifier. |
|
10821 * |
|
10822 * | Byte | Description | Length | |
|
10823 * | 1 | Item Identifier Tag | 1 | |
|
10824 * | 2 | Lenth = 01 | 1 | |
|
10825 * | 3 | Identifier of Item chosen | 1 | |
|
10826 */ |
|
10827 retrieveItemId: function(length) { |
|
10828 let itemId = { |
|
10829 identifier: this.context.GsmPDUHelper.readHexOctet() |
|
10830 }; |
|
10831 return itemId; |
|
10832 }, |
|
10833 |
|
10834 /** |
|
10835 * Response Length. |
|
10836 * |
|
10837 * | Byte | Description | Length | |
|
10838 * | 1 | Response Length Tag | 1 | |
|
10839 * | 2 | Lenth = 02 | 1 | |
|
10840 * | 3 | Minimum length of response | 1 | |
|
10841 * | 4 | Maximum length of response | 1 | |
|
10842 */ |
|
10843 retrieveResponseLength: function(length) { |
|
10844 let GsmPDUHelper = this.context.GsmPDUHelper; |
|
10845 let rspLength = { |
|
10846 minLength : GsmPDUHelper.readHexOctet(), |
|
10847 maxLength : GsmPDUHelper.readHexOctet() |
|
10848 }; |
|
10849 return rspLength; |
|
10850 }, |
|
10851 |
|
10852 /** |
|
10853 * File List. |
|
10854 * |
|
10855 * | Byte | Description | Length | |
|
10856 * | 1 | File List Tag | 1 | |
|
10857 * | 2 ~ (Y-1)+2 | Length (X) | Y | |
|
10858 * | (Y-1)+3 | Number of files | 1 | |
|
10859 * | (Y-1)+4 ~ | Files | X | |
|
10860 * | (Y-1)+X+2 | | | |
|
10861 */ |
|
10862 retrieveFileList: function(length) { |
|
10863 let num = this.context.GsmPDUHelper.readHexOctet(); |
|
10864 let fileList = ""; |
|
10865 length--; // -1 for the num octet. |
|
10866 for (let i = 0; i < 2 * length; i++) { |
|
10867 // Didn't use readHexOctet here, |
|
10868 // otherwise 0x00 will be "0", not "00" |
|
10869 fileList += String.fromCharCode(this.context.Buf.readUint16()); |
|
10870 } |
|
10871 return { |
|
10872 fileList: fileList |
|
10873 }; |
|
10874 }, |
|
10875 |
|
10876 /** |
|
10877 * Default Text. |
|
10878 * |
|
10879 * Same as Text String. |
|
10880 */ |
|
10881 retrieveDefaultText: function(length) { |
|
10882 return this.retrieveTextString(length); |
|
10883 }, |
|
10884 |
|
10885 /** |
|
10886 * Event List. |
|
10887 */ |
|
10888 retrieveEventList: function(length) { |
|
10889 if (!length) { |
|
10890 // null means an indication to ME to remove the existing list of events |
|
10891 // in ME. |
|
10892 return null; |
|
10893 } |
|
10894 |
|
10895 let GsmPDUHelper = this.context.GsmPDUHelper; |
|
10896 let eventList = []; |
|
10897 for (let i = 0; i < length; i++) { |
|
10898 eventList.push(GsmPDUHelper.readHexOctet()); |
|
10899 } |
|
10900 return { |
|
10901 eventList: eventList |
|
10902 }; |
|
10903 }, |
|
10904 |
|
10905 /** |
|
10906 * Timer Identifier. |
|
10907 * |
|
10908 * | Byte | Description | Length | |
|
10909 * | 1 | Timer Identifier Tag | 1 | |
|
10910 * | 2 | Length = 01 | 1 | |
|
10911 * | 3 | Timer Identifier | 1 | |
|
10912 */ |
|
10913 retrieveTimerId: function(length) { |
|
10914 let id = { |
|
10915 timerId: this.context.GsmPDUHelper.readHexOctet() |
|
10916 }; |
|
10917 return id; |
|
10918 }, |
|
10919 |
|
10920 /** |
|
10921 * Timer Value. |
|
10922 * |
|
10923 * | Byte | Description | Length | |
|
10924 * | 1 | Timer Value Tag | 1 | |
|
10925 * | 2 | Length = 03 | 1 | |
|
10926 * | 3 | Hour | 1 | |
|
10927 * | 4 | Minute | 1 | |
|
10928 * | 5 | Second | 1 | |
|
10929 */ |
|
10930 retrieveTimerValue: function(length) { |
|
10931 let GsmPDUHelper = this.context.GsmPDUHelper; |
|
10932 let value = { |
|
10933 timerValue: (GsmPDUHelper.readSwappedNibbleBcdNum(1) * 60 * 60) + |
|
10934 (GsmPDUHelper.readSwappedNibbleBcdNum(1) * 60) + |
|
10935 (GsmPDUHelper.readSwappedNibbleBcdNum(1)) |
|
10936 }; |
|
10937 return value; |
|
10938 }, |
|
10939 |
|
10940 /** |
|
10941 * Immediate Response. |
|
10942 * |
|
10943 * | Byte | Description | Length | |
|
10944 * | 1 | Immediate Response Tag | 1 | |
|
10945 * | 2 | Length = 00 | 1 | |
|
10946 */ |
|
10947 retrieveImmediaResponse: function(length) { |
|
10948 return {}; |
|
10949 }, |
|
10950 |
|
10951 /** |
|
10952 * URL |
|
10953 * |
|
10954 * | Byte | Description | Length | |
|
10955 * | 1 | URL Tag | 1 | |
|
10956 * | 2 ~ (Y+1) | Length(X) | Y | |
|
10957 * | (Y+2) ~ | URL | X | |
|
10958 * | (Y+1+X) | | | |
|
10959 */ |
|
10960 retrieveUrl: function(length) { |
|
10961 let GsmPDUHelper = this.context.GsmPDUHelper; |
|
10962 let s = ""; |
|
10963 for (let i = 0; i < length; i++) { |
|
10964 s += String.fromCharCode(GsmPDUHelper.readHexOctet()); |
|
10965 } |
|
10966 return {url: s}; |
|
10967 }, |
|
10968 |
|
10969 /** |
|
10970 * Next Action Indicator List. |
|
10971 * |
|
10972 * | Byte | Description | Length | |
|
10973 * | 1 | Next Action tag | 1 | |
|
10974 * | 1 | Length(X) | 1 | |
|
10975 * | 3~ | Next Action List | X | |
|
10976 * | 3+X-1 | | | |
|
10977 */ |
|
10978 retrieveNextActionList: function(length) { |
|
10979 let GsmPDUHelper = this.context.GsmPDUHelper; |
|
10980 let nextActionList = []; |
|
10981 for (let i = 0; i < length; i++) { |
|
10982 nextActionList.push(GsmPDUHelper.readHexOctet()); |
|
10983 } |
|
10984 return nextActionList; |
|
10985 }, |
|
10986 |
|
10987 searchForTag: function(tag, ctlvs) { |
|
10988 let iter = Iterator(ctlvs); |
|
10989 return this.searchForNextTag(tag, iter); |
|
10990 }, |
|
10991 |
|
10992 searchForNextTag: function(tag, iter) { |
|
10993 for (let [index, ctlv] in iter) { |
|
10994 if ((ctlv.tag & ~COMPREHENSIONTLV_FLAG_CR) == tag) { |
|
10995 return ctlv; |
|
10996 } |
|
10997 } |
|
10998 return null; |
|
10999 }, |
|
11000 }; |
|
11001 StkProactiveCmdHelperObject.prototype[COMPREHENSIONTLV_TAG_COMMAND_DETAILS] = function COMPREHENSIONTLV_TAG_COMMAND_DETAILS(length) { |
|
11002 return this.retrieveCommandDetails(length); |
|
11003 }; |
|
11004 StkProactiveCmdHelperObject.prototype[COMPREHENSIONTLV_TAG_DEVICE_ID] = function COMPREHENSIONTLV_TAG_DEVICE_ID(length) { |
|
11005 return this.retrieveDeviceId(length); |
|
11006 }; |
|
11007 StkProactiveCmdHelperObject.prototype[COMPREHENSIONTLV_TAG_ALPHA_ID] = function COMPREHENSIONTLV_TAG_ALPHA_ID(length) { |
|
11008 return this.retrieveAlphaId(length); |
|
11009 }; |
|
11010 StkProactiveCmdHelperObject.prototype[COMPREHENSIONTLV_TAG_DURATION] = function COMPREHENSIONTLV_TAG_DURATION(length) { |
|
11011 return this.retrieveDuration(length); |
|
11012 }; |
|
11013 StkProactiveCmdHelperObject.prototype[COMPREHENSIONTLV_TAG_ADDRESS] = function COMPREHENSIONTLV_TAG_ADDRESS(length) { |
|
11014 return this.retrieveAddress(length); |
|
11015 }; |
|
11016 StkProactiveCmdHelperObject.prototype[COMPREHENSIONTLV_TAG_TEXT_STRING] = function COMPREHENSIONTLV_TAG_TEXT_STRING(length) { |
|
11017 return this.retrieveTextString(length); |
|
11018 }; |
|
11019 StkProactiveCmdHelperObject.prototype[COMPREHENSIONTLV_TAG_TONE] = function COMPREHENSIONTLV_TAG_TONE(length) { |
|
11020 return this.retrieveTone(length); |
|
11021 }; |
|
11022 StkProactiveCmdHelperObject.prototype[COMPREHENSIONTLV_TAG_ITEM] = function COMPREHENSIONTLV_TAG_ITEM(length) { |
|
11023 return this.retrieveItem(length); |
|
11024 }; |
|
11025 StkProactiveCmdHelperObject.prototype[COMPREHENSIONTLV_TAG_ITEM_ID] = function COMPREHENSIONTLV_TAG_ITEM_ID(length) { |
|
11026 return this.retrieveItemId(length); |
|
11027 }; |
|
11028 StkProactiveCmdHelperObject.prototype[COMPREHENSIONTLV_TAG_RESPONSE_LENGTH] = function COMPREHENSIONTLV_TAG_RESPONSE_LENGTH(length) { |
|
11029 return this.retrieveResponseLength(length); |
|
11030 }; |
|
11031 StkProactiveCmdHelperObject.prototype[COMPREHENSIONTLV_TAG_FILE_LIST] = function COMPREHENSIONTLV_TAG_FILE_LIST(length) { |
|
11032 return this.retrieveFileList(length); |
|
11033 }; |
|
11034 StkProactiveCmdHelperObject.prototype[COMPREHENSIONTLV_TAG_DEFAULT_TEXT] = function COMPREHENSIONTLV_TAG_DEFAULT_TEXT(length) { |
|
11035 return this.retrieveDefaultText(length); |
|
11036 }; |
|
11037 StkProactiveCmdHelperObject.prototype[COMPREHENSIONTLV_TAG_EVENT_LIST] = function COMPREHENSIONTLV_TAG_EVENT_LIST(length) { |
|
11038 return this.retrieveEventList(length); |
|
11039 }; |
|
11040 StkProactiveCmdHelperObject.prototype[COMPREHENSIONTLV_TAG_TIMER_IDENTIFIER] = function COMPREHENSIONTLV_TAG_TIMER_IDENTIFIER(length) { |
|
11041 return this.retrieveTimerId(length); |
|
11042 }; |
|
11043 StkProactiveCmdHelperObject.prototype[COMPREHENSIONTLV_TAG_TIMER_VALUE] = function COMPREHENSIONTLV_TAG_TIMER_VALUE(length) { |
|
11044 return this.retrieveTimerValue(length); |
|
11045 }; |
|
11046 StkProactiveCmdHelperObject.prototype[COMPREHENSIONTLV_TAG_IMMEDIATE_RESPONSE] = function COMPREHENSIONTLV_TAG_IMMEDIATE_RESPONSE(length) { |
|
11047 return this.retrieveImmediaResponse(length); |
|
11048 }; |
|
11049 StkProactiveCmdHelperObject.prototype[COMPREHENSIONTLV_TAG_URL] = function COMPREHENSIONTLV_TAG_URL(length) { |
|
11050 return this.retrieveUrl(length); |
|
11051 }; |
|
11052 StkProactiveCmdHelperObject.prototype[COMPREHENSIONTLV_TAG_NEXT_ACTION_IND] = function COMPREHENSIONTLV_TAG_NEXT_ACTION_IND(length) { |
|
11053 return this.retrieveNextActionList(length); |
|
11054 }; |
|
11055 |
|
11056 function ComprehensionTlvHelperObject(aContext) { |
|
11057 this.context = aContext; |
|
11058 } |
|
11059 ComprehensionTlvHelperObject.prototype = { |
|
11060 context: null, |
|
11061 |
|
11062 /** |
|
11063 * Decode raw data to a Comprehension-TLV. |
|
11064 */ |
|
11065 decode: function() { |
|
11066 let GsmPDUHelper = this.context.GsmPDUHelper; |
|
11067 |
|
11068 let hlen = 0; // For header(tag field + length field) length. |
|
11069 let temp = GsmPDUHelper.readHexOctet(); |
|
11070 hlen++; |
|
11071 |
|
11072 // TS 101.220, clause 7.1.1 |
|
11073 let tag, cr; |
|
11074 switch (temp) { |
|
11075 // TS 101.220, clause 7.1.1 |
|
11076 case 0x0: // Not used. |
|
11077 case 0xff: // Not used. |
|
11078 case 0x80: // Reserved for future use. |
|
11079 throw new Error("Invalid octet when parsing Comprehension TLV :" + temp); |
|
11080 case 0x7f: // Tag is three byte format. |
|
11081 // TS 101.220 clause 7.1.1.2. |
|
11082 // | Byte 1 | Byte 2 | Byte 3 | |
|
11083 // | | 8 | 7 | 6 | 5 | 4 | 3 | 2 | 1 | | |
|
11084 // | 0x7f |CR | Tag Value | |
|
11085 tag = (GsmPDUHelper.readHexOctet() << 8) | GsmPDUHelper.readHexOctet(); |
|
11086 hlen += 2; |
|
11087 cr = (tag & 0x8000) !== 0; |
|
11088 tag &= ~0x8000; |
|
11089 break; |
|
11090 default: // Tag is single byte format. |
|
11091 tag = temp; |
|
11092 // TS 101.220 clause 7.1.1.1. |
|
11093 // | 8 | 7 | 6 | 5 | 4 | 3 | 2 | 1 | |
|
11094 // |CR | Tag Value | |
|
11095 cr = (tag & 0x80) !== 0; |
|
11096 tag &= ~0x80; |
|
11097 } |
|
11098 |
|
11099 // TS 101.220 clause 7.1.2, Length Encoding. |
|
11100 // Length | Byte 1 | Byte 2 | Byte 3 | Byte 4 | |
|
11101 // 0 - 127 | 00 - 7f | N/A | N/A | N/A | |
|
11102 // 128-255 | 81 | 80 - ff| N/A | N/A | |
|
11103 // 256-65535| 82 | 0100 - ffff | N/A | |
|
11104 // 65536- | 83 | 010000 - ffffff | |
|
11105 // 16777215 |
|
11106 // |
|
11107 // Length errors: TS 11.14, clause 6.10.6 |
|
11108 |
|
11109 let length; // Data length. |
|
11110 temp = GsmPDUHelper.readHexOctet(); |
|
11111 hlen++; |
|
11112 if (temp < 0x80) { |
|
11113 length = temp; |
|
11114 } else if (temp == 0x81) { |
|
11115 length = GsmPDUHelper.readHexOctet(); |
|
11116 hlen++; |
|
11117 if (length < 0x80) { |
|
11118 throw new Error("Invalid length in Comprehension TLV :" + length); |
|
11119 } |
|
11120 } else if (temp == 0x82) { |
|
11121 length = (GsmPDUHelper.readHexOctet() << 8) | GsmPDUHelper.readHexOctet(); |
|
11122 hlen += 2; |
|
11123 if (lenth < 0x0100) { |
|
11124 throw new Error("Invalid length in 3-byte Comprehension TLV :" + length); |
|
11125 } |
|
11126 } else if (temp == 0x83) { |
|
11127 length = (GsmPDUHelper.readHexOctet() << 16) | |
|
11128 (GsmPDUHelper.readHexOctet() << 8) | |
|
11129 GsmPDUHelper.readHexOctet(); |
|
11130 hlen += 3; |
|
11131 if (length < 0x010000) { |
|
11132 throw new Error("Invalid length in 4-byte Comprehension TLV :" + length); |
|
11133 } |
|
11134 } else { |
|
11135 throw new Error("Invalid octet in Comprehension TLV :" + temp); |
|
11136 } |
|
11137 |
|
11138 let ctlv = { |
|
11139 tag: tag, |
|
11140 length: length, |
|
11141 value: this.context.StkProactiveCmdHelper.retrieve(tag, length), |
|
11142 cr: cr, |
|
11143 hlen: hlen |
|
11144 }; |
|
11145 return ctlv; |
|
11146 }, |
|
11147 |
|
11148 decodeChunks: function(length) { |
|
11149 let chunks = []; |
|
11150 let index = 0; |
|
11151 while (index < length) { |
|
11152 let tlv = this.decode(); |
|
11153 chunks.push(tlv); |
|
11154 index += tlv.length; |
|
11155 index += tlv.hlen; |
|
11156 } |
|
11157 return chunks; |
|
11158 }, |
|
11159 |
|
11160 /** |
|
11161 * Write Location Info Comprehension TLV. |
|
11162 * |
|
11163 * @param loc location Information. |
|
11164 */ |
|
11165 writeLocationInfoTlv: function(loc) { |
|
11166 let GsmPDUHelper = this.context.GsmPDUHelper; |
|
11167 |
|
11168 GsmPDUHelper.writeHexOctet(COMPREHENSIONTLV_TAG_LOCATION_INFO | |
|
11169 COMPREHENSIONTLV_FLAG_CR); |
|
11170 GsmPDUHelper.writeHexOctet(loc.gsmCellId > 0xffff ? 9 : 7); |
|
11171 // From TS 11.14, clause 12.19 |
|
11172 // "The mobile country code (MCC), the mobile network code (MNC), |
|
11173 // the location area code (LAC) and the cell ID are |
|
11174 // coded as in TS 04.08." |
|
11175 // And from TS 04.08 and TS 24.008, |
|
11176 // the format is as follows: |
|
11177 // |
|
11178 // MCC = MCC_digit_1 + MCC_digit_2 + MCC_digit_3 |
|
11179 // |
|
11180 // 8 7 6 5 4 3 2 1 |
|
11181 // +-------------+-------------+ |
|
11182 // | MCC digit 2 | MCC digit 1 | octet 2 |
|
11183 // | MNC digit 3 | MCC digit 3 | octet 3 |
|
11184 // | MNC digit 2 | MNC digit 1 | octet 4 |
|
11185 // +-------------+-------------+ |
|
11186 // |
|
11187 // Also in TS 24.008 |
|
11188 // "However a network operator may decide to |
|
11189 // use only two digits in the MNC in the LAI over the |
|
11190 // radio interface. In this case, bits 5 to 8 of octet 3 |
|
11191 // shall be coded as '1111'". |
|
11192 |
|
11193 // MCC & MNC, 3 octets |
|
11194 let mcc = loc.mcc, mnc; |
|
11195 if (loc.mnc.length == 2) { |
|
11196 mnc = "F" + loc.mnc; |
|
11197 } else { |
|
11198 mnc = loc.mnc[2] + loc.mnc[0] + loc.mnc[1]; |
|
11199 } |
|
11200 GsmPDUHelper.writeSwappedNibbleBCD(mcc + mnc); |
|
11201 |
|
11202 // LAC, 2 octets |
|
11203 GsmPDUHelper.writeHexOctet((loc.gsmLocationAreaCode >> 8) & 0xff); |
|
11204 GsmPDUHelper.writeHexOctet(loc.gsmLocationAreaCode & 0xff); |
|
11205 |
|
11206 // Cell Id |
|
11207 if (loc.gsmCellId > 0xffff) { |
|
11208 // UMTS/WCDMA, gsmCellId is 28 bits. |
|
11209 GsmPDUHelper.writeHexOctet((loc.gsmCellId >> 24) & 0xff); |
|
11210 GsmPDUHelper.writeHexOctet((loc.gsmCellId >> 16) & 0xff); |
|
11211 GsmPDUHelper.writeHexOctet((loc.gsmCellId >> 8) & 0xff); |
|
11212 GsmPDUHelper.writeHexOctet(loc.gsmCellId & 0xff); |
|
11213 } else { |
|
11214 // GSM, gsmCellId is 16 bits. |
|
11215 GsmPDUHelper.writeHexOctet((loc.gsmCellId >> 8) & 0xff); |
|
11216 GsmPDUHelper.writeHexOctet(loc.gsmCellId & 0xff); |
|
11217 } |
|
11218 }, |
|
11219 |
|
11220 /** |
|
11221 * Given a geckoError string, this function translates it into cause value |
|
11222 * and write the value into buffer. |
|
11223 * |
|
11224 * @param geckoError Error string that is passed to gecko. |
|
11225 */ |
|
11226 writeCauseTlv: function(geckoError) { |
|
11227 let GsmPDUHelper = this.context.GsmPDUHelper; |
|
11228 |
|
11229 let cause = -1; |
|
11230 for (let errorNo in RIL_ERROR_TO_GECKO_ERROR) { |
|
11231 if (geckoError == RIL_ERROR_TO_GECKO_ERROR[errorNo]) { |
|
11232 cause = errorNo; |
|
11233 break; |
|
11234 } |
|
11235 } |
|
11236 cause = (cause == -1) ? ERROR_SUCCESS : cause; |
|
11237 |
|
11238 GsmPDUHelper.writeHexOctet(COMPREHENSIONTLV_TAG_CAUSE | |
|
11239 COMPREHENSIONTLV_FLAG_CR); |
|
11240 GsmPDUHelper.writeHexOctet(2); // For single cause value. |
|
11241 |
|
11242 // TS 04.08, clause 10.5.4.11: National standard code + user location. |
|
11243 GsmPDUHelper.writeHexOctet(0x60); |
|
11244 |
|
11245 // TS 04.08, clause 10.5.4.11: ext bit = 1 + 7 bits for cause. |
|
11246 // +-----------------+----------------------------------+ |
|
11247 // | Ext = 1 (1 bit) | Cause (7 bits) | |
|
11248 // +-----------------+----------------------------------+ |
|
11249 GsmPDUHelper.writeHexOctet(0x80 | cause); |
|
11250 }, |
|
11251 |
|
11252 writeDateTimeZoneTlv: function(date) { |
|
11253 let GsmPDUHelper = this.context.GsmPDUHelper; |
|
11254 |
|
11255 GsmPDUHelper.writeHexOctet(COMPREHENSIONTLV_TAG_DATE_TIME_ZONE); |
|
11256 GsmPDUHelper.writeHexOctet(7); |
|
11257 GsmPDUHelper.writeTimestamp(date); |
|
11258 }, |
|
11259 |
|
11260 writeLanguageTlv: function(language) { |
|
11261 let GsmPDUHelper = this.context.GsmPDUHelper; |
|
11262 |
|
11263 GsmPDUHelper.writeHexOctet(COMPREHENSIONTLV_TAG_LANGUAGE); |
|
11264 GsmPDUHelper.writeHexOctet(2); |
|
11265 |
|
11266 // ISO 639-1, Alpha-2 code |
|
11267 // TS 123.038, clause 6.2.1, GSM 7 bit Default Alphabet |
|
11268 GsmPDUHelper.writeHexOctet( |
|
11269 PDU_NL_LOCKING_SHIFT_TABLES[PDU_NL_IDENTIFIER_DEFAULT].indexOf(language[0])); |
|
11270 GsmPDUHelper.writeHexOctet( |
|
11271 PDU_NL_LOCKING_SHIFT_TABLES[PDU_NL_IDENTIFIER_DEFAULT].indexOf(language[1])); |
|
11272 }, |
|
11273 |
|
11274 /** |
|
11275 * Write Timer Value Comprehension TLV. |
|
11276 * |
|
11277 * @param seconds length of time during of the timer. |
|
11278 * @param cr Comprehension Required or not |
|
11279 */ |
|
11280 writeTimerValueTlv: function(seconds, cr) { |
|
11281 let GsmPDUHelper = this.context.GsmPDUHelper; |
|
11282 |
|
11283 GsmPDUHelper.writeHexOctet(COMPREHENSIONTLV_TAG_TIMER_VALUE | |
|
11284 (cr ? COMPREHENSIONTLV_FLAG_CR : 0)); |
|
11285 GsmPDUHelper.writeHexOctet(3); |
|
11286 |
|
11287 // TS 102.223, clause 8.38 |
|
11288 // +----------------+------------------+-------------------+ |
|
11289 // | hours (1 byte) | minutes (1 btye) | secounds (1 byte) | |
|
11290 // +----------------+------------------+-------------------+ |
|
11291 GsmPDUHelper.writeSwappedNibbleBCDNum(Math.floor(seconds / 60 / 60)); |
|
11292 GsmPDUHelper.writeSwappedNibbleBCDNum(Math.floor(seconds / 60) % 60); |
|
11293 GsmPDUHelper.writeSwappedNibbleBCDNum(seconds % 60); |
|
11294 }, |
|
11295 |
|
11296 getSizeOfLengthOctets: function(length) { |
|
11297 if (length >= 0x10000) { |
|
11298 return 4; // 0x83, len_1, len_2, len_3 |
|
11299 } else if (length >= 0x100) { |
|
11300 return 3; // 0x82, len_1, len_2 |
|
11301 } else if (length >= 0x80) { |
|
11302 return 2; // 0x81, len |
|
11303 } else { |
|
11304 return 1; // len |
|
11305 } |
|
11306 }, |
|
11307 |
|
11308 writeLength: function(length) { |
|
11309 let GsmPDUHelper = this.context.GsmPDUHelper; |
|
11310 |
|
11311 // TS 101.220 clause 7.1.2, Length Encoding. |
|
11312 // Length | Byte 1 | Byte 2 | Byte 3 | Byte 4 | |
|
11313 // 0 - 127 | 00 - 7f | N/A | N/A | N/A | |
|
11314 // 128-255 | 81 | 80 - ff| N/A | N/A | |
|
11315 // 256-65535| 82 | 0100 - ffff | N/A | |
|
11316 // 65536- | 83 | 010000 - ffffff | |
|
11317 // 16777215 |
|
11318 if (length < 0x80) { |
|
11319 GsmPDUHelper.writeHexOctet(length); |
|
11320 } else if (0x80 <= length && length < 0x100) { |
|
11321 GsmPDUHelper.writeHexOctet(0x81); |
|
11322 GsmPDUHelper.writeHexOctet(length); |
|
11323 } else if (0x100 <= length && length < 0x10000) { |
|
11324 GsmPDUHelper.writeHexOctet(0x82); |
|
11325 GsmPDUHelper.writeHexOctet((length >> 8) & 0xff); |
|
11326 GsmPDUHelper.writeHexOctet(length & 0xff); |
|
11327 } else if (0x10000 <= length && length < 0x1000000) { |
|
11328 GsmPDUHelper.writeHexOctet(0x83); |
|
11329 GsmPDUHelper.writeHexOctet((length >> 16) & 0xff); |
|
11330 GsmPDUHelper.writeHexOctet((length >> 8) & 0xff); |
|
11331 GsmPDUHelper.writeHexOctet(length & 0xff); |
|
11332 } else { |
|
11333 throw new Error("Invalid length value :" + length); |
|
11334 } |
|
11335 }, |
|
11336 }; |
|
11337 |
|
11338 function BerTlvHelperObject(aContext) { |
|
11339 this.context = aContext; |
|
11340 } |
|
11341 BerTlvHelperObject.prototype = { |
|
11342 context: null, |
|
11343 |
|
11344 /** |
|
11345 * Decode Ber TLV. |
|
11346 * |
|
11347 * @param dataLen |
|
11348 * The length of data in bytes. |
|
11349 */ |
|
11350 decode: function(dataLen) { |
|
11351 let GsmPDUHelper = this.context.GsmPDUHelper; |
|
11352 |
|
11353 let hlen = 0; |
|
11354 let tag = GsmPDUHelper.readHexOctet(); |
|
11355 hlen++; |
|
11356 |
|
11357 // The length is coded onto 1 or 2 bytes. |
|
11358 // Length | Byte 1 | Byte 2 |
|
11359 // 0 - 127 | length ('00' to '7f') | N/A |
|
11360 // 128 - 255 | '81' | length ('80' to 'ff') |
|
11361 let length; |
|
11362 let temp = GsmPDUHelper.readHexOctet(); |
|
11363 hlen++; |
|
11364 if (temp < 0x80) { |
|
11365 length = temp; |
|
11366 } else if (temp === 0x81) { |
|
11367 length = GsmPDUHelper.readHexOctet(); |
|
11368 hlen++; |
|
11369 if (length < 0x80) { |
|
11370 throw new Error("Invalid length " + length); |
|
11371 } |
|
11372 } else { |
|
11373 throw new Error("Invalid length octet " + temp); |
|
11374 } |
|
11375 |
|
11376 // Header + body length check. |
|
11377 if (dataLen - hlen !== length) { |
|
11378 throw new Error("Unexpected BerTlvHelper value length!!"); |
|
11379 } |
|
11380 |
|
11381 let method = this[tag]; |
|
11382 if (typeof method != "function") { |
|
11383 throw new Error("Unknown Ber tag 0x" + tag.toString(16)); |
|
11384 } |
|
11385 |
|
11386 let value = method.call(this, length); |
|
11387 |
|
11388 return { |
|
11389 tag: tag, |
|
11390 length: length, |
|
11391 value: value |
|
11392 }; |
|
11393 }, |
|
11394 |
|
11395 /** |
|
11396 * Process the value part for FCP template TLV. |
|
11397 * |
|
11398 * @param length |
|
11399 * The length of data in bytes. |
|
11400 */ |
|
11401 processFcpTemplate: function(length) { |
|
11402 let tlvs = this.decodeChunks(length); |
|
11403 return tlvs; |
|
11404 }, |
|
11405 |
|
11406 /** |
|
11407 * Process the value part for proactive command TLV. |
|
11408 * |
|
11409 * @param length |
|
11410 * The length of data in bytes. |
|
11411 */ |
|
11412 processProactiveCommand: function(length) { |
|
11413 let ctlvs = this.context.ComprehensionTlvHelper.decodeChunks(length); |
|
11414 return ctlvs; |
|
11415 }, |
|
11416 |
|
11417 /** |
|
11418 * Decode raw data to a Ber-TLV. |
|
11419 */ |
|
11420 decodeInnerTlv: function() { |
|
11421 let GsmPDUHelper = this.context.GsmPDUHelper; |
|
11422 let tag = GsmPDUHelper.readHexOctet(); |
|
11423 let length = GsmPDUHelper.readHexOctet(); |
|
11424 return { |
|
11425 tag: tag, |
|
11426 length: length, |
|
11427 value: this.retrieve(tag, length) |
|
11428 }; |
|
11429 }, |
|
11430 |
|
11431 decodeChunks: function(length) { |
|
11432 let chunks = []; |
|
11433 let index = 0; |
|
11434 while (index < length) { |
|
11435 let tlv = this.decodeInnerTlv(); |
|
11436 if (tlv.value) { |
|
11437 chunks.push(tlv); |
|
11438 } |
|
11439 index += tlv.length; |
|
11440 // tag + length fields consume 2 bytes. |
|
11441 index += 2; |
|
11442 } |
|
11443 return chunks; |
|
11444 }, |
|
11445 |
|
11446 retrieve: function(tag, length) { |
|
11447 let method = this[tag]; |
|
11448 if (typeof method != "function") { |
|
11449 if (DEBUG) { |
|
11450 this.context.debug("Unknown Ber tag : 0x" + tag.toString(16)); |
|
11451 } |
|
11452 let Buf = this.context.Buf; |
|
11453 Buf.seekIncoming(length * Buf.PDU_HEX_OCTET_SIZE); |
|
11454 return null; |
|
11455 } |
|
11456 return method.call(this, length); |
|
11457 }, |
|
11458 |
|
11459 /** |
|
11460 * File Size Data. |
|
11461 * |
|
11462 * | Byte | Description | Length | |
|
11463 * | 1 | Tag | 1 | |
|
11464 * | 2 | Length | 1 | |
|
11465 * | 3 to X+24 | Number of allocated data bytes in the file | X | |
|
11466 * | | , excluding structural information | | |
|
11467 */ |
|
11468 retrieveFileSizeData: function(length) { |
|
11469 let GsmPDUHelper = this.context.GsmPDUHelper; |
|
11470 let fileSizeData = 0; |
|
11471 for (let i = 0; i < length; i++) { |
|
11472 fileSizeData = fileSizeData << 8; |
|
11473 fileSizeData += GsmPDUHelper.readHexOctet(); |
|
11474 } |
|
11475 |
|
11476 return {fileSizeData: fileSizeData}; |
|
11477 }, |
|
11478 |
|
11479 /** |
|
11480 * File Descriptor. |
|
11481 * |
|
11482 * | Byte | Description | Length | |
|
11483 * | 1 | Tag | 1 | |
|
11484 * | 2 | Length | 1 | |
|
11485 * | 3 | File descriptor byte | 1 | |
|
11486 * | 4 | Data coding byte | 1 | |
|
11487 * | 5 ~ 6 | Record length | 2 | |
|
11488 * | 7 | Number of records | 1 | |
|
11489 */ |
|
11490 retrieveFileDescriptor: function(length) { |
|
11491 let GsmPDUHelper = this.context.GsmPDUHelper; |
|
11492 let fileDescriptorByte = GsmPDUHelper.readHexOctet(); |
|
11493 let dataCodingByte = GsmPDUHelper.readHexOctet(); |
|
11494 // See TS 102 221 Table 11.5, we only care the least 3 bits for the |
|
11495 // structure of file. |
|
11496 let fileStructure = fileDescriptorByte & 0x07; |
|
11497 |
|
11498 let fileDescriptor = { |
|
11499 fileStructure: fileStructure |
|
11500 }; |
|
11501 // byte 5 ~ 7 are mandatory for linear fixed and cyclic files, otherwise |
|
11502 // they are not applicable. |
|
11503 if (fileStructure === UICC_EF_STRUCTURE[EF_TYPE_LINEAR_FIXED] || |
|
11504 fileStructure === UICC_EF_STRUCTURE[EF_TYPE_CYCLIC]) { |
|
11505 fileDescriptor.recordLength = (GsmPDUHelper.readHexOctet() << 8) + |
|
11506 GsmPDUHelper.readHexOctet(); |
|
11507 fileDescriptor.numOfRecords = GsmPDUHelper.readHexOctet(); |
|
11508 } |
|
11509 |
|
11510 return fileDescriptor; |
|
11511 }, |
|
11512 |
|
11513 /** |
|
11514 * File identifier. |
|
11515 * |
|
11516 * | Byte | Description | Length | |
|
11517 * | 1 | Tag | 1 | |
|
11518 * | 2 | Length | 1 | |
|
11519 * | 3 ~ 4 | File identifier | 2 | |
|
11520 */ |
|
11521 retrieveFileIdentifier: function(length) { |
|
11522 let GsmPDUHelper = this.context.GsmPDUHelper; |
|
11523 return {fileId : (GsmPDUHelper.readHexOctet() << 8) + |
|
11524 GsmPDUHelper.readHexOctet()}; |
|
11525 }, |
|
11526 |
|
11527 searchForNextTag: function(tag, iter) { |
|
11528 for (let [index, tlv] in iter) { |
|
11529 if (tlv.tag === tag) { |
|
11530 return tlv; |
|
11531 } |
|
11532 } |
|
11533 return null; |
|
11534 } |
|
11535 }; |
|
11536 BerTlvHelperObject.prototype[BER_FCP_TEMPLATE_TAG] = function BER_FCP_TEMPLATE_TAG(length) { |
|
11537 return this.processFcpTemplate(length); |
|
11538 }; |
|
11539 BerTlvHelperObject.prototype[BER_PROACTIVE_COMMAND_TAG] = function BER_PROACTIVE_COMMAND_TAG(length) { |
|
11540 return this.processProactiveCommand(length); |
|
11541 }; |
|
11542 BerTlvHelperObject.prototype[BER_FCP_FILE_SIZE_DATA_TAG] = function BER_FCP_FILE_SIZE_DATA_TAG(length) { |
|
11543 return this.retrieveFileSizeData(length); |
|
11544 }; |
|
11545 BerTlvHelperObject.prototype[BER_FCP_FILE_DESCRIPTOR_TAG] = function BER_FCP_FILE_DESCRIPTOR_TAG(length) { |
|
11546 return this.retrieveFileDescriptor(length); |
|
11547 }; |
|
11548 BerTlvHelperObject.prototype[BER_FCP_FILE_IDENTIFIER_TAG] = function BER_FCP_FILE_IDENTIFIER_TAG(length) { |
|
11549 return this.retrieveFileIdentifier(length); |
|
11550 }; |
|
11551 |
|
11552 /** |
|
11553 * ICC Helper for getting EF path. |
|
11554 */ |
|
11555 function ICCFileHelperObject(aContext) { |
|
11556 this.context = aContext; |
|
11557 } |
|
11558 ICCFileHelperObject.prototype = { |
|
11559 context: null, |
|
11560 |
|
11561 /** |
|
11562 * This function handles only EFs that are common to RUIM, SIM, USIM |
|
11563 * and other types of ICC cards. |
|
11564 */ |
|
11565 getCommonEFPath: function(fileId) { |
|
11566 switch (fileId) { |
|
11567 case ICC_EF_ICCID: |
|
11568 return EF_PATH_MF_SIM; |
|
11569 case ICC_EF_ADN: |
|
11570 return EF_PATH_MF_SIM + EF_PATH_DF_TELECOM; |
|
11571 case ICC_EF_PBR: |
|
11572 return EF_PATH_MF_SIM + EF_PATH_DF_TELECOM + EF_PATH_DF_PHONEBOOK; |
|
11573 } |
|
11574 return null; |
|
11575 }, |
|
11576 |
|
11577 /** |
|
11578 * This function handles EFs for SIM. |
|
11579 */ |
|
11580 getSimEFPath: function(fileId) { |
|
11581 switch (fileId) { |
|
11582 case ICC_EF_FDN: |
|
11583 case ICC_EF_MSISDN: |
|
11584 case ICC_EF_SMS: |
|
11585 return EF_PATH_MF_SIM + EF_PATH_DF_TELECOM; |
|
11586 case ICC_EF_AD: |
|
11587 case ICC_EF_MBDN: |
|
11588 case ICC_EF_MWIS: |
|
11589 case ICC_EF_PLMNsel: |
|
11590 case ICC_EF_SPN: |
|
11591 case ICC_EF_SPDI: |
|
11592 case ICC_EF_SST: |
|
11593 case ICC_EF_PHASE: |
|
11594 case ICC_EF_CBMI: |
|
11595 case ICC_EF_CBMID: |
|
11596 case ICC_EF_CBMIR: |
|
11597 case ICC_EF_OPL: |
|
11598 case ICC_EF_PNN: |
|
11599 return EF_PATH_MF_SIM + EF_PATH_DF_GSM; |
|
11600 default: |
|
11601 return null; |
|
11602 } |
|
11603 }, |
|
11604 |
|
11605 /** |
|
11606 * This function handles EFs for USIM. |
|
11607 */ |
|
11608 getUSimEFPath: function(fileId) { |
|
11609 switch (fileId) { |
|
11610 case ICC_EF_AD: |
|
11611 case ICC_EF_FDN: |
|
11612 case ICC_EF_MBDN: |
|
11613 case ICC_EF_MWIS: |
|
11614 case ICC_EF_UST: |
|
11615 case ICC_EF_MSISDN: |
|
11616 case ICC_EF_SPN: |
|
11617 case ICC_EF_SPDI: |
|
11618 case ICC_EF_CBMI: |
|
11619 case ICC_EF_CBMID: |
|
11620 case ICC_EF_CBMIR: |
|
11621 case ICC_EF_OPL: |
|
11622 case ICC_EF_PNN: |
|
11623 case ICC_EF_SMS: |
|
11624 return EF_PATH_MF_SIM + EF_PATH_ADF_USIM; |
|
11625 default: |
|
11626 // The file ids in USIM phone book entries are decided by the |
|
11627 // card manufacturer. So if we don't match any of the cases |
|
11628 // above and if its a USIM return the phone book path. |
|
11629 return EF_PATH_MF_SIM + EF_PATH_DF_TELECOM + EF_PATH_DF_PHONEBOOK; |
|
11630 } |
|
11631 }, |
|
11632 |
|
11633 /** |
|
11634 * This function handles EFs for RUIM |
|
11635 */ |
|
11636 getRuimEFPath: function(fileId) { |
|
11637 switch(fileId) { |
|
11638 case ICC_EF_CSIM_IMSI_M: |
|
11639 case ICC_EF_CSIM_CDMAHOME: |
|
11640 case ICC_EF_CSIM_CST: |
|
11641 case ICC_EF_CSIM_SPN: |
|
11642 return EF_PATH_MF_SIM + EF_PATH_DF_CDMA; |
|
11643 case ICC_EF_FDN: |
|
11644 return EF_PATH_MF_SIM + EF_PATH_DF_TELECOM; |
|
11645 default: |
|
11646 return null; |
|
11647 } |
|
11648 }, |
|
11649 |
|
11650 /** |
|
11651 * Helper function for getting the pathId for the specific ICC record |
|
11652 * depeding on which type of ICC card we are using. |
|
11653 * |
|
11654 * @param fileId |
|
11655 * File id. |
|
11656 * @return The pathId or null in case of an error or invalid input. |
|
11657 */ |
|
11658 getEFPath: function(fileId) { |
|
11659 let appType = this.context.RIL.appType; |
|
11660 if (appType == null) { |
|
11661 return null; |
|
11662 } |
|
11663 |
|
11664 let path = this.getCommonEFPath(fileId); |
|
11665 if (path) { |
|
11666 return path; |
|
11667 } |
|
11668 |
|
11669 switch (appType) { |
|
11670 case CARD_APPTYPE_SIM: |
|
11671 return this.getSimEFPath(fileId); |
|
11672 case CARD_APPTYPE_USIM: |
|
11673 return this.getUSimEFPath(fileId); |
|
11674 case CARD_APPTYPE_RUIM: |
|
11675 return this.getRuimEFPath(fileId); |
|
11676 default: |
|
11677 return null; |
|
11678 } |
|
11679 } |
|
11680 }; |
|
11681 |
|
11682 /** |
|
11683 * Helper for ICC IO functionalities. |
|
11684 */ |
|
11685 function ICCIOHelperObject(aContext) { |
|
11686 this.context = aContext; |
|
11687 } |
|
11688 ICCIOHelperObject.prototype = { |
|
11689 context: null, |
|
11690 |
|
11691 /** |
|
11692 * Load EF with type 'Linear Fixed'. |
|
11693 * |
|
11694 * @param fileId |
|
11695 * The file to operate on, one of the ICC_EF_* constants. |
|
11696 * @param recordNumber [optional] |
|
11697 * The number of the record shall be loaded. |
|
11698 * @param recordSize [optional] |
|
11699 * The size of the record. |
|
11700 * @param callback [optional] |
|
11701 * The callback function shall be called when the record(s) is read. |
|
11702 * @param onerror [optional] |
|
11703 * The callback function shall be called when failure. |
|
11704 */ |
|
11705 loadLinearFixedEF: function(options) { |
|
11706 let cb; |
|
11707 let readRecord = (function(options) { |
|
11708 options.command = ICC_COMMAND_READ_RECORD; |
|
11709 options.p1 = options.recordNumber || 1; // Record number |
|
11710 options.p2 = READ_RECORD_ABSOLUTE_MODE; |
|
11711 options.p3 = options.recordSize; |
|
11712 options.callback = cb || options.callback; |
|
11713 this.context.RIL.iccIO(options); |
|
11714 }).bind(this); |
|
11715 |
|
11716 options.type = EF_TYPE_LINEAR_FIXED; |
|
11717 options.pathId = this.context.ICCFileHelper.getEFPath(options.fileId); |
|
11718 if (options.recordSize) { |
|
11719 readRecord(options); |
|
11720 return; |
|
11721 } |
|
11722 |
|
11723 cb = options.callback; |
|
11724 options.callback = readRecord; |
|
11725 this.getResponse(options); |
|
11726 }, |
|
11727 |
|
11728 /** |
|
11729 * Load next record from current record Id. |
|
11730 */ |
|
11731 loadNextRecord: function(options) { |
|
11732 options.p1++; |
|
11733 this.context.RIL.iccIO(options); |
|
11734 }, |
|
11735 |
|
11736 /** |
|
11737 * Update EF with type 'Linear Fixed'. |
|
11738 * |
|
11739 * @param fileId |
|
11740 * The file to operate on, one of the ICC_EF_* constants. |
|
11741 * @param recordNumber |
|
11742 * The number of the record shall be updated. |
|
11743 * @param dataWriter [optional] |
|
11744 * The function for writing string parameter for the ICC_COMMAND_UPDATE_RECORD. |
|
11745 * @param pin2 [optional] |
|
11746 * PIN2 is required when updating ICC_EF_FDN. |
|
11747 * @param callback [optional] |
|
11748 * The callback function shall be called when the record is updated. |
|
11749 * @param onerror [optional] |
|
11750 * The callback function shall be called when failure. |
|
11751 */ |
|
11752 updateLinearFixedEF: function(options) { |
|
11753 if (!options.fileId || !options.recordNumber) { |
|
11754 throw new Error("Unexpected fileId " + options.fileId + |
|
11755 " or recordNumber " + options.recordNumber); |
|
11756 } |
|
11757 |
|
11758 options.type = EF_TYPE_LINEAR_FIXED; |
|
11759 options.pathId = this.context.ICCFileHelper.getEFPath(options.fileId); |
|
11760 let cb = options.callback; |
|
11761 options.callback = function callback(options) { |
|
11762 options.callback = cb; |
|
11763 options.command = ICC_COMMAND_UPDATE_RECORD; |
|
11764 options.p1 = options.recordNumber; |
|
11765 options.p2 = READ_RECORD_ABSOLUTE_MODE; |
|
11766 options.p3 = options.recordSize; |
|
11767 this.context.RIL.iccIO(options); |
|
11768 }.bind(this); |
|
11769 this.getResponse(options); |
|
11770 }, |
|
11771 |
|
11772 /** |
|
11773 * Load EF with type 'Transparent'. |
|
11774 * |
|
11775 * @param fileId |
|
11776 * The file to operate on, one of the ICC_EF_* constants. |
|
11777 * @param callback [optional] |
|
11778 * The callback function shall be called when the record(s) is read. |
|
11779 * @param onerror [optional] |
|
11780 * The callback function shall be called when failure. |
|
11781 */ |
|
11782 loadTransparentEF: function(options) { |
|
11783 options.type = EF_TYPE_TRANSPARENT; |
|
11784 let cb = options.callback; |
|
11785 options.callback = function callback(options) { |
|
11786 options.callback = cb; |
|
11787 options.command = ICC_COMMAND_READ_BINARY; |
|
11788 options.p3 = options.fileSize; |
|
11789 this.context.RIL.iccIO(options); |
|
11790 }.bind(this); |
|
11791 this.getResponse(options); |
|
11792 }, |
|
11793 |
|
11794 /** |
|
11795 * Use ICC_COMMAND_GET_RESPONSE to query the EF. |
|
11796 * |
|
11797 * @param fileId |
|
11798 * The file to operate on, one of the ICC_EF_* constants. |
|
11799 */ |
|
11800 getResponse: function(options) { |
|
11801 options.command = ICC_COMMAND_GET_RESPONSE; |
|
11802 options.pathId = options.pathId || |
|
11803 this.context.ICCFileHelper.getEFPath(options.fileId); |
|
11804 if (!options.pathId) { |
|
11805 throw new Error("Unknown pathId for " + options.fileId.toString(16)); |
|
11806 } |
|
11807 options.p1 = 0; // For GET_RESPONSE, p1 = 0 |
|
11808 options.p2 = 0; // For GET_RESPONSE, p2 = 0 |
|
11809 options.p3 = GET_RESPONSE_EF_SIZE_BYTES; |
|
11810 this.context.RIL.iccIO(options); |
|
11811 }, |
|
11812 |
|
11813 /** |
|
11814 * Process ICC I/O response. |
|
11815 */ |
|
11816 processICCIO: function(options) { |
|
11817 let func = this[options.command]; |
|
11818 func.call(this, options); |
|
11819 }, |
|
11820 |
|
11821 /** |
|
11822 * Process a ICC_COMMAND_GET_RESPONSE type command for REQUEST_SIM_IO. |
|
11823 */ |
|
11824 processICCIOGetResponse: function(options) { |
|
11825 let Buf = this.context.Buf; |
|
11826 let strLen = Buf.readInt32(); |
|
11827 |
|
11828 let peek = this.context.GsmPDUHelper.readHexOctet(); |
|
11829 Buf.seekIncoming(-1 * Buf.PDU_HEX_OCTET_SIZE); |
|
11830 if (peek === BER_FCP_TEMPLATE_TAG) { |
|
11831 this.processUSimGetResponse(options, strLen / 2); |
|
11832 } else { |
|
11833 this.processSimGetResponse(options); |
|
11834 } |
|
11835 Buf.readStringDelimiter(strLen); |
|
11836 |
|
11837 if (options.callback) { |
|
11838 options.callback(options); |
|
11839 } |
|
11840 }, |
|
11841 |
|
11842 /** |
|
11843 * Helper function for processing USIM get response. |
|
11844 */ |
|
11845 processUSimGetResponse: function(options, octetLen) { |
|
11846 let BerTlvHelper = this.context.BerTlvHelper; |
|
11847 |
|
11848 let berTlv = BerTlvHelper.decode(octetLen); |
|
11849 // See TS 102 221 Table 11.4 for the content order of getResponse. |
|
11850 let iter = Iterator(berTlv.value); |
|
11851 let tlv = BerTlvHelper.searchForNextTag(BER_FCP_FILE_DESCRIPTOR_TAG, |
|
11852 iter); |
|
11853 if (!tlv || (tlv.value.fileStructure !== UICC_EF_STRUCTURE[options.type])) { |
|
11854 throw new Error("Expected EF type " + UICC_EF_STRUCTURE[options.type] + |
|
11855 " but read " + tlv.value.fileStructure); |
|
11856 } |
|
11857 |
|
11858 if (tlv.value.fileStructure === UICC_EF_STRUCTURE[EF_TYPE_LINEAR_FIXED] || |
|
11859 tlv.value.fileStructure === UICC_EF_STRUCTURE[EF_TYPE_CYCLIC]) { |
|
11860 options.recordSize = tlv.value.recordLength; |
|
11861 options.totalRecords = tlv.value.numOfRecords; |
|
11862 } |
|
11863 |
|
11864 tlv = BerTlvHelper.searchForNextTag(BER_FCP_FILE_IDENTIFIER_TAG, iter); |
|
11865 if (!tlv || (tlv.value.fileId !== options.fileId)) { |
|
11866 throw new Error("Expected file ID " + options.fileId.toString(16) + |
|
11867 " but read " + fileId.toString(16)); |
|
11868 } |
|
11869 |
|
11870 tlv = BerTlvHelper.searchForNextTag(BER_FCP_FILE_SIZE_DATA_TAG, iter); |
|
11871 if (!tlv) { |
|
11872 throw new Error("Unexpected file size data"); |
|
11873 } |
|
11874 options.fileSize = tlv.value.fileSizeData; |
|
11875 }, |
|
11876 |
|
11877 /** |
|
11878 * Helper function for processing SIM get response. |
|
11879 */ |
|
11880 processSimGetResponse: function(options) { |
|
11881 let Buf = this.context.Buf; |
|
11882 let GsmPDUHelper = this.context.GsmPDUHelper; |
|
11883 |
|
11884 // The format is from TS 51.011, clause 9.2.1 |
|
11885 |
|
11886 // Skip RFU, data[0] data[1]. |
|
11887 Buf.seekIncoming(2 * Buf.PDU_HEX_OCTET_SIZE); |
|
11888 |
|
11889 // File size, data[2], data[3] |
|
11890 options.fileSize = (GsmPDUHelper.readHexOctet() << 8) | |
|
11891 GsmPDUHelper.readHexOctet(); |
|
11892 |
|
11893 // 2 bytes File id. data[4], data[5] |
|
11894 let fileId = (GsmPDUHelper.readHexOctet() << 8) | |
|
11895 GsmPDUHelper.readHexOctet(); |
|
11896 if (fileId != options.fileId) { |
|
11897 throw new Error("Expected file ID " + options.fileId.toString(16) + |
|
11898 " but read " + fileId.toString(16)); |
|
11899 } |
|
11900 |
|
11901 // Type of file, data[6] |
|
11902 let fileType = GsmPDUHelper.readHexOctet(); |
|
11903 if (fileType != TYPE_EF) { |
|
11904 throw new Error("Unexpected file type " + fileType); |
|
11905 } |
|
11906 |
|
11907 // Skip 1 byte RFU, data[7], |
|
11908 // 3 bytes Access conditions, data[8] data[9] data[10], |
|
11909 // 1 byte File status, data[11], |
|
11910 // 1 byte Length of the following data, data[12]. |
|
11911 Buf.seekIncoming(((RESPONSE_DATA_STRUCTURE - RESPONSE_DATA_FILE_TYPE - 1) * |
|
11912 Buf.PDU_HEX_OCTET_SIZE)); |
|
11913 |
|
11914 // Read Structure of EF, data[13] |
|
11915 let efType = GsmPDUHelper.readHexOctet(); |
|
11916 if (efType != options.type) { |
|
11917 throw new Error("Expected EF type " + options.type + " but read " + efType); |
|
11918 } |
|
11919 |
|
11920 // TODO: Bug 952025. |
|
11921 // Length of a record, data[14]. |
|
11922 // Only available for LINEAR_FIXED and CYCLIC. |
|
11923 if (efType == EF_TYPE_LINEAR_FIXED || efType == EF_TYPE_CYCLIC) { |
|
11924 options.recordSize = GsmPDUHelper.readHexOctet(); |
|
11925 options.totalRecords = options.fileSize / options.recordSize; |
|
11926 } else { |
|
11927 Buf.seekIncoming(1 * Buf.PDU_HEX_OCTET_SIZE); |
|
11928 } |
|
11929 }, |
|
11930 |
|
11931 /** |
|
11932 * Process a ICC_COMMAND_READ_RECORD type command for REQUEST_SIM_IO. |
|
11933 */ |
|
11934 processICCIOReadRecord: function(options) { |
|
11935 if (options.callback) { |
|
11936 options.callback(options); |
|
11937 } |
|
11938 }, |
|
11939 |
|
11940 /** |
|
11941 * Process a ICC_COMMAND_READ_BINARY type command for REQUEST_SIM_IO. |
|
11942 */ |
|
11943 processICCIOReadBinary: function(options) { |
|
11944 if (options.callback) { |
|
11945 options.callback(options); |
|
11946 } |
|
11947 }, |
|
11948 |
|
11949 /** |
|
11950 * Process a ICC_COMMAND_UPDATE_RECORD type command for REQUEST_SIM_IO. |
|
11951 */ |
|
11952 processICCIOUpdateRecord: function(options) { |
|
11953 if (options.callback) { |
|
11954 options.callback(options); |
|
11955 } |
|
11956 }, |
|
11957 |
|
11958 /** |
|
11959 * Process ICC IO error. |
|
11960 */ |
|
11961 processICCIOError: function(options) { |
|
11962 let requestError = RIL_ERROR_TO_GECKO_ERROR[options.rilRequestError]; |
|
11963 if (DEBUG) { |
|
11964 // See GSM11.11, TS 51.011 clause 9.4, and ISO 7816-4 for the error |
|
11965 // description. |
|
11966 let errorMsg = "ICC I/O Error code " + requestError + |
|
11967 " EF id = " + options.fileId.toString(16) + |
|
11968 " command = " + options.command.toString(16); |
|
11969 if (options.sw1 && options.sw2) { |
|
11970 errorMsg += "(" + options.sw1.toString(16) + |
|
11971 "/" + options.sw2.toString(16) + ")"; |
|
11972 } |
|
11973 this.context.debug(errorMsg); |
|
11974 } |
|
11975 if (options.onerror) { |
|
11976 options.onerror(requestError); |
|
11977 } |
|
11978 }, |
|
11979 }; |
|
11980 ICCIOHelperObject.prototype[ICC_COMMAND_SEEK] = null; |
|
11981 ICCIOHelperObject.prototype[ICC_COMMAND_READ_BINARY] = function ICC_COMMAND_READ_BINARY(options) { |
|
11982 this.processICCIOReadBinary(options); |
|
11983 }; |
|
11984 ICCIOHelperObject.prototype[ICC_COMMAND_READ_RECORD] = function ICC_COMMAND_READ_RECORD(options) { |
|
11985 this.processICCIOReadRecord(options); |
|
11986 }; |
|
11987 ICCIOHelperObject.prototype[ICC_COMMAND_GET_RESPONSE] = function ICC_COMMAND_GET_RESPONSE(options) { |
|
11988 this.processICCIOGetResponse(options); |
|
11989 }; |
|
11990 ICCIOHelperObject.prototype[ICC_COMMAND_UPDATE_BINARY] = null; |
|
11991 ICCIOHelperObject.prototype[ICC_COMMAND_UPDATE_RECORD] = function ICC_COMMAND_UPDATE_RECORD(options) { |
|
11992 this.processICCIOUpdateRecord(options); |
|
11993 }; |
|
11994 |
|
11995 /** |
|
11996 * Helper for ICC records. |
|
11997 */ |
|
11998 function ICCRecordHelperObject(aContext) { |
|
11999 this.context = aContext; |
|
12000 } |
|
12001 ICCRecordHelperObject.prototype = { |
|
12002 context: null, |
|
12003 |
|
12004 /** |
|
12005 * Fetch ICC records. |
|
12006 */ |
|
12007 fetchICCRecords: function() { |
|
12008 switch (this.context.RIL.appType) { |
|
12009 case CARD_APPTYPE_SIM: |
|
12010 case CARD_APPTYPE_USIM: |
|
12011 this.context.SimRecordHelper.fetchSimRecords(); |
|
12012 break; |
|
12013 case CARD_APPTYPE_RUIM: |
|
12014 this.context.RuimRecordHelper.fetchRuimRecords(); |
|
12015 break; |
|
12016 } |
|
12017 }, |
|
12018 |
|
12019 /** |
|
12020 * Read the ICCID. |
|
12021 */ |
|
12022 readICCID: function() { |
|
12023 function callback() { |
|
12024 let Buf = this.context.Buf; |
|
12025 let RIL = this.context.RIL; |
|
12026 |
|
12027 let strLen = Buf.readInt32(); |
|
12028 let octetLen = strLen / 2; |
|
12029 RIL.iccInfo.iccid = |
|
12030 this.context.GsmPDUHelper.readSwappedNibbleBcdString(octetLen, true); |
|
12031 // Consumes the remaining buffer if any. |
|
12032 let unReadBuffer = this.context.Buf.getReadAvailable() - |
|
12033 this.context.Buf.PDU_HEX_OCTET_SIZE; |
|
12034 if (unReadBuffer > 0) { |
|
12035 this.context.Buf.seekIncoming(unReadBuffer); |
|
12036 } |
|
12037 Buf.readStringDelimiter(strLen); |
|
12038 |
|
12039 if (DEBUG) this.context.debug("ICCID: " + RIL.iccInfo.iccid); |
|
12040 if (RIL.iccInfo.iccid) { |
|
12041 this.context.ICCUtilsHelper.handleICCInfoChange(); |
|
12042 RIL.reportStkServiceIsRunning(); |
|
12043 } |
|
12044 } |
|
12045 |
|
12046 this.context.ICCIOHelper.loadTransparentEF({ |
|
12047 fileId: ICC_EF_ICCID, |
|
12048 callback: callback.bind(this) |
|
12049 }); |
|
12050 }, |
|
12051 |
|
12052 /** |
|
12053 * Read ICC ADN like EF, i.e. EF_ADN, EF_FDN. |
|
12054 * |
|
12055 * @param fileId EF id of the ADN or FDN. |
|
12056 * @param onsuccess Callback to be called when success. |
|
12057 * @param onerror Callback to be called when error. |
|
12058 */ |
|
12059 readADNLike: function(fileId, onsuccess, onerror) { |
|
12060 let ICCIOHelper = this.context.ICCIOHelper; |
|
12061 |
|
12062 function callback(options) { |
|
12063 let contact = |
|
12064 this.context.ICCPDUHelper.readAlphaIdDiallingNumber(options.recordSize); |
|
12065 if (contact) { |
|
12066 contact.recordId = options.p1; |
|
12067 contacts.push(contact); |
|
12068 } |
|
12069 |
|
12070 if (options.p1 < options.totalRecords) { |
|
12071 ICCIOHelper.loadNextRecord(options); |
|
12072 } else { |
|
12073 if (DEBUG) { |
|
12074 for (let i = 0; i < contacts.length; i++) { |
|
12075 this.context.debug("contact [" + i + "] " + |
|
12076 JSON.stringify(contacts[i])); |
|
12077 } |
|
12078 } |
|
12079 if (onsuccess) { |
|
12080 onsuccess(contacts); |
|
12081 } |
|
12082 } |
|
12083 } |
|
12084 |
|
12085 let contacts = []; |
|
12086 ICCIOHelper.loadLinearFixedEF({fileId: fileId, |
|
12087 callback: callback.bind(this), |
|
12088 onerror: onerror}); |
|
12089 }, |
|
12090 |
|
12091 /** |
|
12092 * Update ICC ADN like EFs, like EF_ADN, EF_FDN. |
|
12093 * |
|
12094 * @param fileId EF id of the ADN or FDN. |
|
12095 * @param contact The contact will be updated. (Shall have recordId property) |
|
12096 * @param pin2 PIN2 is required when updating ICC_EF_FDN. |
|
12097 * @param onsuccess Callback to be called when success. |
|
12098 * @param onerror Callback to be called when error. |
|
12099 */ |
|
12100 updateADNLike: function(fileId, contact, pin2, onsuccess, onerror) { |
|
12101 function dataWriter(recordSize) { |
|
12102 this.context.ICCPDUHelper.writeAlphaIdDiallingNumber(recordSize, |
|
12103 contact.alphaId, |
|
12104 contact.number); |
|
12105 } |
|
12106 |
|
12107 function callback(options) { |
|
12108 if (onsuccess) { |
|
12109 onsuccess(); |
|
12110 } |
|
12111 } |
|
12112 |
|
12113 if (!contact || !contact.recordId) { |
|
12114 if (onerror) onerror(GECKO_ERROR_INVALID_PARAMETER); |
|
12115 return; |
|
12116 } |
|
12117 |
|
12118 this.context.ICCIOHelper.updateLinearFixedEF({ |
|
12119 fileId: fileId, |
|
12120 recordNumber: contact.recordId, |
|
12121 dataWriter: dataWriter.bind(this), |
|
12122 pin2: pin2, |
|
12123 callback: callback.bind(this), |
|
12124 onerror: onerror |
|
12125 }); |
|
12126 }, |
|
12127 |
|
12128 /** |
|
12129 * Read USIM/RUIM Phonebook. |
|
12130 * |
|
12131 * @param onsuccess Callback to be called when success. |
|
12132 * @param onerror Callback to be called when error. |
|
12133 */ |
|
12134 readPBR: function(onsuccess, onerror) { |
|
12135 let Buf = this.context.Buf; |
|
12136 let GsmPDUHelper = this.context.GsmPDUHelper; |
|
12137 let ICCIOHelper = this.context.ICCIOHelper; |
|
12138 let ICCUtilsHelper = this.context.ICCUtilsHelper; |
|
12139 let RIL = this.context.RIL; |
|
12140 |
|
12141 function callback(options) { |
|
12142 let strLen = Buf.readInt32(); |
|
12143 let octetLen = strLen / 2, readLen = 0; |
|
12144 |
|
12145 let pbrTlvs = []; |
|
12146 while (readLen < octetLen) { |
|
12147 let tag = GsmPDUHelper.readHexOctet(); |
|
12148 if (tag == 0xff) { |
|
12149 readLen++; |
|
12150 Buf.seekIncoming((octetLen - readLen) * Buf.PDU_HEX_OCTET_SIZE); |
|
12151 break; |
|
12152 } |
|
12153 |
|
12154 let tlvLen = GsmPDUHelper.readHexOctet(); |
|
12155 let tlvs = ICCUtilsHelper.decodeSimTlvs(tlvLen); |
|
12156 pbrTlvs.push({tag: tag, |
|
12157 length: tlvLen, |
|
12158 value: tlvs}); |
|
12159 |
|
12160 readLen += tlvLen + 2; // +2 for tag and tlvLen |
|
12161 } |
|
12162 Buf.readStringDelimiter(strLen); |
|
12163 |
|
12164 if (pbrTlvs.length > 0) { |
|
12165 let pbr = ICCUtilsHelper.parsePbrTlvs(pbrTlvs); |
|
12166 // EF_ADN is mandatory if and only if DF_PHONEBOOK is present. |
|
12167 if (!pbr.adn) { |
|
12168 if (onerror) onerror("Cannot access ADN."); |
|
12169 return; |
|
12170 } |
|
12171 pbrs.push(pbr); |
|
12172 } |
|
12173 |
|
12174 if (options.p1 < options.totalRecords) { |
|
12175 ICCIOHelper.loadNextRecord(options); |
|
12176 } else { |
|
12177 if (onsuccess) { |
|
12178 RIL.iccInfoPrivate.pbrs = pbrs; |
|
12179 onsuccess(pbrs); |
|
12180 } |
|
12181 } |
|
12182 } |
|
12183 |
|
12184 if (RIL.iccInfoPrivate.pbrs) { |
|
12185 onsuccess(RIL.iccInfoPrivate.pbrs); |
|
12186 return; |
|
12187 } |
|
12188 |
|
12189 let pbrs = []; |
|
12190 ICCIOHelper.loadLinearFixedEF({fileId : ICC_EF_PBR, |
|
12191 callback: callback.bind(this), |
|
12192 onerror: onerror}); |
|
12193 }, |
|
12194 |
|
12195 /** |
|
12196 * Cache EF_IAP record size. |
|
12197 */ |
|
12198 _iapRecordSize: null, |
|
12199 |
|
12200 /** |
|
12201 * Read ICC EF_IAP. (Index Administration Phonebook) |
|
12202 * |
|
12203 * @see TS 131.102, clause 4.4.2.2 |
|
12204 * |
|
12205 * @param fileId EF id of the IAP. |
|
12206 * @param recordNumber The number of the record shall be loaded. |
|
12207 * @param onsuccess Callback to be called when success. |
|
12208 * @param onerror Callback to be called when error. |
|
12209 */ |
|
12210 readIAP: function(fileId, recordNumber, onsuccess, onerror) { |
|
12211 function callback(options) { |
|
12212 let Buf = this.context.Buf; |
|
12213 let strLen = Buf.readInt32(); |
|
12214 let octetLen = strLen / 2; |
|
12215 this._iapRecordSize = options.recordSize; |
|
12216 |
|
12217 let iap = this.context.GsmPDUHelper.readHexOctetArray(octetLen); |
|
12218 Buf.readStringDelimiter(strLen); |
|
12219 |
|
12220 if (onsuccess) { |
|
12221 onsuccess(iap); |
|
12222 } |
|
12223 } |
|
12224 |
|
12225 this.context.ICCIOHelper.loadLinearFixedEF({ |
|
12226 fileId: fileId, |
|
12227 recordNumber: recordNumber, |
|
12228 recordSize: this._iapRecordSize, |
|
12229 callback: callback.bind(this), |
|
12230 onerror: onerror |
|
12231 }); |
|
12232 }, |
|
12233 |
|
12234 /** |
|
12235 * Update USIM/RUIM Phonebook EF_IAP. |
|
12236 * |
|
12237 * @see TS 131.102, clause 4.4.2.13 |
|
12238 * |
|
12239 * @param fileId EF id of the IAP. |
|
12240 * @param recordNumber The identifier of the record shall be updated. |
|
12241 * @param iap The IAP value to be written. |
|
12242 * @param onsuccess Callback to be called when success. |
|
12243 * @param onerror Callback to be called when error. |
|
12244 */ |
|
12245 updateIAP: function(fileId, recordNumber, iap, onsuccess, onerror) { |
|
12246 let dataWriter = function dataWriter(recordSize) { |
|
12247 let Buf = this.context.Buf; |
|
12248 let GsmPDUHelper = this.context.GsmPDUHelper; |
|
12249 |
|
12250 // Write String length |
|
12251 let strLen = recordSize * 2; |
|
12252 Buf.writeInt32(strLen); |
|
12253 |
|
12254 for (let i = 0; i < iap.length; i++) { |
|
12255 GsmPDUHelper.writeHexOctet(iap[i]); |
|
12256 } |
|
12257 |
|
12258 Buf.writeStringDelimiter(strLen); |
|
12259 }.bind(this); |
|
12260 |
|
12261 this.context.ICCIOHelper.updateLinearFixedEF({ |
|
12262 fileId: fileId, |
|
12263 recordNumber: recordNumber, |
|
12264 dataWriter: dataWriter, |
|
12265 callback: onsuccess, |
|
12266 onerror: onerror |
|
12267 }); |
|
12268 }, |
|
12269 |
|
12270 /** |
|
12271 * Cache EF_Email record size. |
|
12272 */ |
|
12273 _emailRecordSize: null, |
|
12274 |
|
12275 /** |
|
12276 * Read USIM/RUIM Phonebook EF_EMAIL. |
|
12277 * |
|
12278 * @see TS 131.102, clause 4.4.2.13 |
|
12279 * |
|
12280 * @param fileId EF id of the EMAIL. |
|
12281 * @param fileType The type of the EMAIL, one of the ICC_USIM_TYPE* constants. |
|
12282 * @param recordNumber The number of the record shall be loaded. |
|
12283 * @param onsuccess Callback to be called when success. |
|
12284 * @param onerror Callback to be called when error. |
|
12285 */ |
|
12286 readEmail: function(fileId, fileType, recordNumber, onsuccess, onerror) { |
|
12287 function callback(options) { |
|
12288 let Buf = this.context.Buf; |
|
12289 let ICCPDUHelper = this.context.ICCPDUHelper; |
|
12290 |
|
12291 let strLen = Buf.readInt32(); |
|
12292 let octetLen = strLen / 2; |
|
12293 let email = null; |
|
12294 this._emailRecordSize = options.recordSize; |
|
12295 |
|
12296 // Read contact's email |
|
12297 // |
|
12298 // | Byte | Description | Length | M/O |
|
12299 // | 1 ~ X | E-mail Address | X | M |
|
12300 // | X+1 | ADN file SFI | 1 | C |
|
12301 // | X+2 | ADN file Record Identifier | 1 | C |
|
12302 // Note: The fields marked as C above are mandatort if the file |
|
12303 // is not type 1 (as specified in EF_PBR) |
|
12304 if (fileType == ICC_USIM_TYPE1_TAG) { |
|
12305 email = ICCPDUHelper.read8BitUnpackedToString(octetLen); |
|
12306 } else { |
|
12307 email = ICCPDUHelper.read8BitUnpackedToString(octetLen - 2); |
|
12308 |
|
12309 // Consumes the remaining buffer |
|
12310 Buf.seekIncoming(2 * Buf.PDU_HEX_OCTET_SIZE); // For ADN SFI and Record Identifier |
|
12311 } |
|
12312 |
|
12313 Buf.readStringDelimiter(strLen); |
|
12314 |
|
12315 if (onsuccess) { |
|
12316 onsuccess(email); |
|
12317 } |
|
12318 } |
|
12319 |
|
12320 this.context.ICCIOHelper.loadLinearFixedEF({ |
|
12321 fileId: fileId, |
|
12322 recordNumber: recordNumber, |
|
12323 recordSize: this._emailRecordSize, |
|
12324 callback: callback.bind(this), |
|
12325 onerror: onerror |
|
12326 }); |
|
12327 }, |
|
12328 |
|
12329 /** |
|
12330 * Update USIM/RUIM Phonebook EF_EMAIL. |
|
12331 * |
|
12332 * @see TS 131.102, clause 4.4.2.13 |
|
12333 * |
|
12334 * @param pbr Phonebook Reference File. |
|
12335 * @param recordNumber The identifier of the record shall be updated. |
|
12336 * @param email The value to be written. |
|
12337 * @param adnRecordId The record Id of ADN, only needed if the fileType of Email is TYPE2. |
|
12338 * @param onsuccess Callback to be called when success. |
|
12339 * @param onerror Callback to be called when error. |
|
12340 */ |
|
12341 updateEmail: function(pbr, recordNumber, email, adnRecordId, onsuccess, onerror) { |
|
12342 let fileId = pbr[USIM_PBR_EMAIL].fileId; |
|
12343 let fileType = pbr[USIM_PBR_EMAIL].fileType; |
|
12344 let dataWriter = function dataWriter(recordSize) { |
|
12345 let Buf = this.context.Buf; |
|
12346 let GsmPDUHelper = this.context.GsmPDUHelper; |
|
12347 let ICCPDUHelper = this.context.ICCPDUHelper; |
|
12348 |
|
12349 // Write String length |
|
12350 let strLen = recordSize * 2; |
|
12351 Buf.writeInt32(strLen); |
|
12352 |
|
12353 if (fileType == ICC_USIM_TYPE1_TAG) { |
|
12354 ICCPDUHelper.writeStringTo8BitUnpacked(recordSize, email); |
|
12355 } else { |
|
12356 ICCPDUHelper.writeStringTo8BitUnpacked(recordSize - 2, email); |
|
12357 GsmPDUHelper.writeHexOctet(pbr.adn.sfi || 0xff); |
|
12358 GsmPDUHelper.writeHexOctet(adnRecordId); |
|
12359 } |
|
12360 |
|
12361 Buf.writeStringDelimiter(strLen); |
|
12362 }.bind(this); |
|
12363 |
|
12364 this.context.ICCIOHelper.updateLinearFixedEF({ |
|
12365 fileId: fileId, |
|
12366 recordNumber: recordNumber, |
|
12367 dataWriter: dataWriter, |
|
12368 callback: onsuccess, |
|
12369 onerror: onerror |
|
12370 }); |
|
12371 }, |
|
12372 |
|
12373 /** |
|
12374 * Cache EF_ANR record size. |
|
12375 */ |
|
12376 _anrRecordSize: null, |
|
12377 |
|
12378 /** |
|
12379 * Read USIM/RUIM Phonebook EF_ANR. |
|
12380 * |
|
12381 * @see TS 131.102, clause 4.4.2.9 |
|
12382 * |
|
12383 * @param fileId EF id of the ANR. |
|
12384 * @param fileType One of the ICC_USIM_TYPE* constants. |
|
12385 * @param recordNumber The number of the record shall be loaded. |
|
12386 * @param onsuccess Callback to be called when success. |
|
12387 * @param onerror Callback to be called when error. |
|
12388 */ |
|
12389 readANR: function(fileId, fileType, recordNumber, onsuccess, onerror) { |
|
12390 function callback(options) { |
|
12391 let Buf = this.context.Buf; |
|
12392 let strLen = Buf.readInt32(); |
|
12393 let number = null; |
|
12394 this._anrRecordSize = options.recordSize; |
|
12395 |
|
12396 // Skip EF_AAS Record ID. |
|
12397 Buf.seekIncoming(1 * Buf.PDU_HEX_OCTET_SIZE); |
|
12398 |
|
12399 number = this.context.ICCPDUHelper.readNumberWithLength(); |
|
12400 |
|
12401 // Skip 2 unused octets, CCP and EXT1. |
|
12402 Buf.seekIncoming(2 * Buf.PDU_HEX_OCTET_SIZE); |
|
12403 |
|
12404 // For Type 2 there are two extra octets. |
|
12405 if (fileType == ICC_USIM_TYPE2_TAG) { |
|
12406 // Skip 2 unused octets, ADN SFI and Record Identifier. |
|
12407 Buf.seekIncoming(2 * Buf.PDU_HEX_OCTET_SIZE); |
|
12408 } |
|
12409 |
|
12410 Buf.readStringDelimiter(strLen); |
|
12411 |
|
12412 if (onsuccess) { |
|
12413 onsuccess(number); |
|
12414 } |
|
12415 } |
|
12416 |
|
12417 this.context.ICCIOHelper.loadLinearFixedEF({ |
|
12418 fileId: fileId, |
|
12419 recordNumber: recordNumber, |
|
12420 recordSize: this._anrRecordSize, |
|
12421 callback: callback.bind(this), |
|
12422 onerror: onerror |
|
12423 }); |
|
12424 }, |
|
12425 |
|
12426 /** |
|
12427 * Update USIM/RUIM Phonebook EF_ANR. |
|
12428 * |
|
12429 * @see TS 131.102, clause 4.4.2.9 |
|
12430 * |
|
12431 * @param pbr Phonebook Reference File. |
|
12432 * @param recordNumber The identifier of the record shall be updated. |
|
12433 * @param number The value to be written. |
|
12434 * @param adnRecordId The record Id of ADN, only needed if the fileType of Email is TYPE2. |
|
12435 * @param onsuccess Callback to be called when success. |
|
12436 * @param onerror Callback to be called when error. |
|
12437 */ |
|
12438 updateANR: function(pbr, recordNumber, number, adnRecordId, onsuccess, onerror) { |
|
12439 let fileId = pbr[USIM_PBR_ANR0].fileId; |
|
12440 let fileType = pbr[USIM_PBR_ANR0].fileType; |
|
12441 let dataWriter = function dataWriter(recordSize) { |
|
12442 let Buf = this.context.Buf; |
|
12443 let GsmPDUHelper = this.context.GsmPDUHelper; |
|
12444 |
|
12445 // Write String length |
|
12446 let strLen = recordSize * 2; |
|
12447 Buf.writeInt32(strLen); |
|
12448 |
|
12449 // EF_AAS record Id. Unused for now. |
|
12450 GsmPDUHelper.writeHexOctet(0xff); |
|
12451 |
|
12452 this.context.ICCPDUHelper.writeNumberWithLength(number); |
|
12453 |
|
12454 // Write unused octets 0xff, CCP and EXT1. |
|
12455 GsmPDUHelper.writeHexOctet(0xff); |
|
12456 GsmPDUHelper.writeHexOctet(0xff); |
|
12457 |
|
12458 // For Type 2 there are two extra octets. |
|
12459 if (fileType == ICC_USIM_TYPE2_TAG) { |
|
12460 GsmPDUHelper.writeHexOctet(pbr.adn.sfi || 0xff); |
|
12461 GsmPDUHelper.writeHexOctet(adnRecordId); |
|
12462 } |
|
12463 |
|
12464 Buf.writeStringDelimiter(strLen); |
|
12465 }.bind(this); |
|
12466 |
|
12467 this.context.ICCIOHelper.updateLinearFixedEF({ |
|
12468 fileId: fileId, |
|
12469 recordNumber: recordNumber, |
|
12470 dataWriter: dataWriter, |
|
12471 callback: onsuccess, |
|
12472 onerror: onerror |
|
12473 }); |
|
12474 }, |
|
12475 |
|
12476 /** |
|
12477 * Find free record id. |
|
12478 * |
|
12479 * @param fileId EF id. |
|
12480 * @param onsuccess Callback to be called when success. |
|
12481 * @param onerror Callback to be called when error. |
|
12482 */ |
|
12483 findFreeRecordId: function(fileId, onsuccess, onerror) { |
|
12484 let ICCIOHelper = this.context.ICCIOHelper; |
|
12485 |
|
12486 function callback(options) { |
|
12487 let Buf = this.context.Buf; |
|
12488 let GsmPDUHelper = this.context.GsmPDUHelper; |
|
12489 |
|
12490 let strLen = Buf.readInt32(); |
|
12491 let octetLen = strLen / 2; |
|
12492 let readLen = 0; |
|
12493 |
|
12494 while (readLen < octetLen) { |
|
12495 let octet = GsmPDUHelper.readHexOctet(); |
|
12496 readLen++; |
|
12497 if (octet != 0xff) { |
|
12498 break; |
|
12499 } |
|
12500 } |
|
12501 |
|
12502 if (readLen == octetLen) { |
|
12503 // Find free record. |
|
12504 if (onsuccess) { |
|
12505 onsuccess(options.p1); |
|
12506 } |
|
12507 return; |
|
12508 } else { |
|
12509 Buf.seekIncoming((octetLen - readLen) * Buf.PDU_HEX_OCTET_SIZE); |
|
12510 } |
|
12511 |
|
12512 Buf.readStringDelimiter(strLen); |
|
12513 |
|
12514 if (options.p1 < options.totalRecords) { |
|
12515 ICCIOHelper.loadNextRecord(options); |
|
12516 } else { |
|
12517 // No free record found. |
|
12518 if (DEBUG) { |
|
12519 this.context.debug(CONTACT_ERR_NO_FREE_RECORD_FOUND); |
|
12520 } |
|
12521 onerror(CONTACT_ERR_NO_FREE_RECORD_FOUND); |
|
12522 } |
|
12523 } |
|
12524 |
|
12525 ICCIOHelper.loadLinearFixedEF({fileId: fileId, |
|
12526 callback: callback.bind(this), |
|
12527 onerror: onerror}); |
|
12528 }, |
|
12529 }; |
|
12530 |
|
12531 /** |
|
12532 * Helper for (U)SIM Records. |
|
12533 */ |
|
12534 function SimRecordHelperObject(aContext) { |
|
12535 this.context = aContext; |
|
12536 } |
|
12537 SimRecordHelperObject.prototype = { |
|
12538 context: null, |
|
12539 |
|
12540 /** |
|
12541 * Fetch (U)SIM records. |
|
12542 */ |
|
12543 fetchSimRecords: function() { |
|
12544 this.context.RIL.getIMSI(); |
|
12545 this.readAD(); |
|
12546 this.readSST(); |
|
12547 }, |
|
12548 |
|
12549 /** |
|
12550 * Read EF_phase. |
|
12551 * This EF is only available in SIM. |
|
12552 */ |
|
12553 readSimPhase: function() { |
|
12554 function callback() { |
|
12555 let Buf = this.context.Buf; |
|
12556 let strLen = Buf.readInt32(); |
|
12557 |
|
12558 let GsmPDUHelper = this.context.GsmPDUHelper; |
|
12559 let phase = GsmPDUHelper.readHexOctet(); |
|
12560 // If EF_phase is coded '03' or greater, an ME supporting STK shall |
|
12561 // perform the PROFILE DOWNLOAD procedure. |
|
12562 if (RILQUIRKS_SEND_STK_PROFILE_DOWNLOAD && |
|
12563 phase >= ICC_PHASE_2_PROFILE_DOWNLOAD_REQUIRED) { |
|
12564 this.context.RIL.sendStkTerminalProfile(STK_SUPPORTED_TERMINAL_PROFILE); |
|
12565 } |
|
12566 |
|
12567 Buf.readStringDelimiter(strLen); |
|
12568 } |
|
12569 |
|
12570 this.context.ICCIOHelper.loadTransparentEF({ |
|
12571 fileId: ICC_EF_PHASE, |
|
12572 callback: callback.bind(this) |
|
12573 }); |
|
12574 }, |
|
12575 |
|
12576 /** |
|
12577 * Read the MSISDN from the (U)SIM. |
|
12578 */ |
|
12579 readMSISDN: function() { |
|
12580 function callback(options) { |
|
12581 let RIL = this.context.RIL; |
|
12582 |
|
12583 let contact = |
|
12584 this.context.ICCPDUHelper.readAlphaIdDiallingNumber(options.recordSize); |
|
12585 if (!contact || |
|
12586 (RIL.iccInfo.msisdn !== undefined && |
|
12587 RIL.iccInfo.msisdn === contact.number)) { |
|
12588 return; |
|
12589 } |
|
12590 RIL.iccInfo.msisdn = contact.number; |
|
12591 if (DEBUG) this.context.debug("MSISDN: " + RIL.iccInfo.msisdn); |
|
12592 this.context.ICCUtilsHelper.handleICCInfoChange(); |
|
12593 } |
|
12594 |
|
12595 this.context.ICCIOHelper.loadLinearFixedEF({ |
|
12596 fileId: ICC_EF_MSISDN, |
|
12597 callback: callback.bind(this) |
|
12598 }); |
|
12599 }, |
|
12600 |
|
12601 /** |
|
12602 * Read the AD (Administrative Data) from the (U)SIM. |
|
12603 */ |
|
12604 readAD: function() { |
|
12605 function callback() { |
|
12606 let Buf = this.context.Buf; |
|
12607 let strLen = Buf.readInt32(); |
|
12608 // Each octet is encoded into two chars. |
|
12609 let octetLen = strLen / 2; |
|
12610 let ad = this.context.GsmPDUHelper.readHexOctetArray(octetLen); |
|
12611 Buf.readStringDelimiter(strLen); |
|
12612 |
|
12613 if (DEBUG) { |
|
12614 let str = ""; |
|
12615 for (let i = 0; i < ad.length; i++) { |
|
12616 str += ad[i] + ", "; |
|
12617 } |
|
12618 this.context.debug("AD: " + str); |
|
12619 } |
|
12620 |
|
12621 let ICCUtilsHelper = this.context.ICCUtilsHelper; |
|
12622 let RIL = this.context.RIL; |
|
12623 // The 4th byte of the response is the length of MNC. |
|
12624 let mccMnc = ICCUtilsHelper.parseMccMncFromImsi(RIL.iccInfoPrivate.imsi, |
|
12625 ad && ad[3]); |
|
12626 if (mccMnc) { |
|
12627 RIL.iccInfo.mcc = mccMnc.mcc; |
|
12628 RIL.iccInfo.mnc = mccMnc.mnc; |
|
12629 ICCUtilsHelper.handleICCInfoChange(); |
|
12630 } |
|
12631 } |
|
12632 |
|
12633 this.context.ICCIOHelper.loadTransparentEF({ |
|
12634 fileId: ICC_EF_AD, |
|
12635 callback: callback.bind(this) |
|
12636 }); |
|
12637 }, |
|
12638 |
|
12639 /** |
|
12640 * Read the SPN (Service Provider Name) from the (U)SIM. |
|
12641 */ |
|
12642 readSPN: function() { |
|
12643 function callback() { |
|
12644 let Buf = this.context.Buf; |
|
12645 let strLen = Buf.readInt32(); |
|
12646 // Each octet is encoded into two chars. |
|
12647 let octetLen = strLen / 2; |
|
12648 let spnDisplayCondition = this.context.GsmPDUHelper.readHexOctet(); |
|
12649 // Minus 1 because the first octet is used to store display condition. |
|
12650 let spn = this.context.ICCPDUHelper.readAlphaIdentifier(octetLen - 1); |
|
12651 Buf.readStringDelimiter(strLen); |
|
12652 |
|
12653 if (DEBUG) { |
|
12654 this.context.debug("SPN: spn = " + spn + |
|
12655 ", spnDisplayCondition = " + spnDisplayCondition); |
|
12656 } |
|
12657 |
|
12658 let RIL = this.context.RIL; |
|
12659 RIL.iccInfoPrivate.spnDisplayCondition = spnDisplayCondition; |
|
12660 RIL.iccInfo.spn = spn; |
|
12661 let ICCUtilsHelper = this.context.ICCUtilsHelper; |
|
12662 ICCUtilsHelper.updateDisplayCondition(); |
|
12663 ICCUtilsHelper.handleICCInfoChange(); |
|
12664 } |
|
12665 |
|
12666 this.context.ICCIOHelper.loadTransparentEF({ |
|
12667 fileId: ICC_EF_SPN, |
|
12668 callback: callback.bind(this) |
|
12669 }); |
|
12670 }, |
|
12671 |
|
12672 /** |
|
12673 * Read the (U)SIM Service Table from the (U)SIM. |
|
12674 */ |
|
12675 readSST: function() { |
|
12676 function callback() { |
|
12677 let Buf = this.context.Buf; |
|
12678 let RIL = this.context.RIL; |
|
12679 |
|
12680 let strLen = Buf.readInt32(); |
|
12681 // Each octet is encoded into two chars. |
|
12682 let octetLen = strLen / 2; |
|
12683 let sst = this.context.GsmPDUHelper.readHexOctetArray(octetLen); |
|
12684 Buf.readStringDelimiter(strLen); |
|
12685 RIL.iccInfoPrivate.sst = sst; |
|
12686 if (DEBUG) { |
|
12687 let str = ""; |
|
12688 for (let i = 0; i < sst.length; i++) { |
|
12689 str += sst[i] + ", "; |
|
12690 } |
|
12691 this.context.debug("SST: " + str); |
|
12692 } |
|
12693 |
|
12694 let ICCUtilsHelper = this.context.ICCUtilsHelper; |
|
12695 if (ICCUtilsHelper.isICCServiceAvailable("MSISDN")) { |
|
12696 if (DEBUG) this.context.debug("MSISDN: MSISDN is available"); |
|
12697 this.readMSISDN(); |
|
12698 } else { |
|
12699 if (DEBUG) this.context.debug("MSISDN: MSISDN service is not available"); |
|
12700 } |
|
12701 |
|
12702 // Fetch SPN and PLMN list, if some of them are available. |
|
12703 if (ICCUtilsHelper.isICCServiceAvailable("SPN")) { |
|
12704 if (DEBUG) this.context.debug("SPN: SPN is available"); |
|
12705 this.readSPN(); |
|
12706 } else { |
|
12707 if (DEBUG) this.context.debug("SPN: SPN service is not available"); |
|
12708 } |
|
12709 |
|
12710 if (ICCUtilsHelper.isICCServiceAvailable("MDN")) { |
|
12711 if (DEBUG) this.context.debug("MDN: MDN available."); |
|
12712 this.readMBDN(); |
|
12713 } else { |
|
12714 if (DEBUG) this.context.debug("MDN: MDN service is not available"); |
|
12715 } |
|
12716 |
|
12717 if (ICCUtilsHelper.isICCServiceAvailable("MWIS")) { |
|
12718 if (DEBUG) this.context.debug("MWIS: MWIS is available"); |
|
12719 this.readMWIS(); |
|
12720 } else { |
|
12721 if (DEBUG) this.context.debug("MWIS: MWIS is not available"); |
|
12722 } |
|
12723 |
|
12724 if (ICCUtilsHelper.isICCServiceAvailable("SPDI")) { |
|
12725 if (DEBUG) this.context.debug("SPDI: SPDI available."); |
|
12726 this.readSPDI(); |
|
12727 } else { |
|
12728 if (DEBUG) this.context.debug("SPDI: SPDI service is not available"); |
|
12729 } |
|
12730 |
|
12731 if (ICCUtilsHelper.isICCServiceAvailable("PNN")) { |
|
12732 if (DEBUG) this.context.debug("PNN: PNN is available"); |
|
12733 this.readPNN(); |
|
12734 } else { |
|
12735 if (DEBUG) this.context.debug("PNN: PNN is not available"); |
|
12736 } |
|
12737 |
|
12738 if (ICCUtilsHelper.isICCServiceAvailable("OPL")) { |
|
12739 if (DEBUG) this.context.debug("OPL: OPL is available"); |
|
12740 this.readOPL(); |
|
12741 } else { |
|
12742 if (DEBUG) this.context.debug("OPL: OPL is not available"); |
|
12743 } |
|
12744 |
|
12745 if (ICCUtilsHelper.isICCServiceAvailable("CBMI")) { |
|
12746 this.readCBMI(); |
|
12747 } else { |
|
12748 RIL.cellBroadcastConfigs.CBMI = null; |
|
12749 } |
|
12750 if (ICCUtilsHelper.isICCServiceAvailable("DATA_DOWNLOAD_SMS_CB")) { |
|
12751 this.readCBMID(); |
|
12752 } else { |
|
12753 RIL.cellBroadcastConfigs.CBMID = null; |
|
12754 } |
|
12755 if (ICCUtilsHelper.isICCServiceAvailable("CBMIR")) { |
|
12756 this.readCBMIR(); |
|
12757 } else { |
|
12758 RIL.cellBroadcastConfigs.CBMIR = null; |
|
12759 } |
|
12760 RIL._mergeAllCellBroadcastConfigs(); |
|
12761 } |
|
12762 |
|
12763 // ICC_EF_UST has the same value with ICC_EF_SST. |
|
12764 this.context.ICCIOHelper.loadTransparentEF({ |
|
12765 fileId: ICC_EF_SST, |
|
12766 callback: callback.bind(this) |
|
12767 }); |
|
12768 }, |
|
12769 |
|
12770 /** |
|
12771 * Read (U)SIM MBDN. (Mailbox Dialling Number) |
|
12772 * |
|
12773 * @see TS 131.102, clause 4.2.60 |
|
12774 */ |
|
12775 readMBDN: function() { |
|
12776 function callback(options) { |
|
12777 let RIL = this.context.RIL; |
|
12778 let contact = |
|
12779 this.context.ICCPDUHelper.readAlphaIdDiallingNumber(options.recordSize); |
|
12780 if (!contact || |
|
12781 (RIL.iccInfoPrivate.mbdn !== undefined && |
|
12782 RIL.iccInfoPrivate.mbdn === contact.number)) { |
|
12783 return; |
|
12784 } |
|
12785 RIL.iccInfoPrivate.mbdn = contact.number; |
|
12786 if (DEBUG) { |
|
12787 this.context.debug("MBDN, alphaId=" + contact.alphaId + |
|
12788 " number=" + contact.number); |
|
12789 } |
|
12790 contact.rilMessageType = "iccmbdn"; |
|
12791 RIL.sendChromeMessage(contact); |
|
12792 } |
|
12793 |
|
12794 this.context.ICCIOHelper.loadLinearFixedEF({ |
|
12795 fileId: ICC_EF_MBDN, |
|
12796 callback: callback.bind(this) |
|
12797 }); |
|
12798 }, |
|
12799 |
|
12800 /** |
|
12801 * Read ICC MWIS. (Message Waiting Indication Status) |
|
12802 * |
|
12803 * @see TS 31.102, clause 4.2.63 for USIM and TS 51.011, clause 10.3.45 for SIM. |
|
12804 */ |
|
12805 readMWIS: function() { |
|
12806 function callback(options) { |
|
12807 let Buf = this.context.Buf; |
|
12808 let RIL = this.context.RIL; |
|
12809 |
|
12810 let strLen = Buf.readInt32(); |
|
12811 // Each octet is encoded into two chars. |
|
12812 let octetLen = strLen / 2; |
|
12813 let mwis = this.context.GsmPDUHelper.readHexOctetArray(octetLen); |
|
12814 Buf.readStringDelimiter(strLen); |
|
12815 if (!mwis) { |
|
12816 return; |
|
12817 } |
|
12818 RIL.iccInfoPrivate.mwis = mwis; //Keep raw MWIS for updateMWIS() |
|
12819 |
|
12820 let mwi = {}; |
|
12821 // b8 b7 B6 b5 b4 b3 b2 b1 4.2.63, TS 31.102 version 11.6.0 |
|
12822 // | | | | | | | |__ Voicemail |
|
12823 // | | | | | | |_____ Fax |
|
12824 // | | | | | |________ Electronic Mail |
|
12825 // | | | | |___________ Other |
|
12826 // | | | |______________ Videomail |
|
12827 // |__|__|_________________ RFU |
|
12828 mwi.active = ((mwis[0] & 0x01) != 0); |
|
12829 |
|
12830 if (mwi.active) { |
|
12831 // In TS 23.040 msgCount is in the range from 0 to 255. |
|
12832 // The value 255 shall be taken to mean 255 or greater. |
|
12833 // |
|
12834 // However, There is no definition about 0 when MWI is active. |
|
12835 // |
|
12836 // Normally, when mwi is active, the msgCount must be larger than 0. |
|
12837 // Refer to other reference phone, |
|
12838 // 0 is usually treated as UNKNOWN for storing 2nd level MWI status (DCS). |
|
12839 mwi.msgCount = (mwis[1] === 0) ? GECKO_VOICEMAIL_MESSAGE_COUNT_UNKNOWN |
|
12840 : mwis[1]; |
|
12841 } else { |
|
12842 mwi.msgCount = 0; |
|
12843 } |
|
12844 |
|
12845 RIL.sendChromeMessage({ rilMessageType: "iccmwis", |
|
12846 mwi: mwi }); |
|
12847 } |
|
12848 |
|
12849 this.context.ICCIOHelper.loadLinearFixedEF({ |
|
12850 fileId: ICC_EF_MWIS, |
|
12851 recordNumber: 1, // Get 1st Subscriber Profile. |
|
12852 callback: callback.bind(this) |
|
12853 }); |
|
12854 }, |
|
12855 |
|
12856 /** |
|
12857 * Update ICC MWIS. (Message Waiting Indication Status) |
|
12858 * |
|
12859 * @see TS 31.102, clause 4.2.63 for USIM and TS 51.011, clause 10.3.45 for SIM. |
|
12860 */ |
|
12861 updateMWIS: function(mwi) { |
|
12862 let RIL = this.context.RIL; |
|
12863 if (!RIL.iccInfoPrivate.mwis) { |
|
12864 return; |
|
12865 } |
|
12866 |
|
12867 function dataWriter(recordSize) { |
|
12868 let mwis = RIL.iccInfoPrivate.mwis; |
|
12869 |
|
12870 let msgCount = |
|
12871 (mwi.msgCount === GECKO_VOICEMAIL_MESSAGE_COUNT_UNKNOWN) ? 0 : mwi.msgCount; |
|
12872 |
|
12873 [mwis[0], mwis[1]] = (mwi.active) ? [(mwis[0] | 0x01), msgCount] |
|
12874 : [(mwis[0] & 0xFE), 0]; |
|
12875 |
|
12876 let strLen = recordSize * 2; |
|
12877 let Buf = this.context.Buf; |
|
12878 Buf.writeInt32(strLen); |
|
12879 |
|
12880 let GsmPDUHelper = this.context.GsmPDUHelper; |
|
12881 for (let i = 0; i < mwis.length; i++) { |
|
12882 GsmPDUHelper.writeHexOctet(mwis[i]); |
|
12883 } |
|
12884 |
|
12885 Buf.writeStringDelimiter(strLen); |
|
12886 } |
|
12887 |
|
12888 this.context.ICCIOHelper.updateLinearFixedEF({ |
|
12889 fileId: ICC_EF_MWIS, |
|
12890 recordNumber: 1, // Update 1st Subscriber Profile. |
|
12891 dataWriter: dataWriter.bind(this) |
|
12892 }); |
|
12893 }, |
|
12894 |
|
12895 /** |
|
12896 * Read the SPDI (Service Provider Display Information) from the (U)SIM. |
|
12897 * |
|
12898 * See TS 131.102 section 4.2.66 for USIM and TS 51.011 section 10.3.50 |
|
12899 * for SIM. |
|
12900 */ |
|
12901 readSPDI: function() { |
|
12902 function callback() { |
|
12903 let Buf = this.context.Buf; |
|
12904 let strLen = Buf.readInt32(); |
|
12905 let octetLen = strLen / 2; |
|
12906 let readLen = 0; |
|
12907 let endLoop = false; |
|
12908 |
|
12909 let RIL = this.context.RIL; |
|
12910 RIL.iccInfoPrivate.SPDI = null; |
|
12911 |
|
12912 let GsmPDUHelper = this.context.GsmPDUHelper; |
|
12913 while ((readLen < octetLen) && !endLoop) { |
|
12914 let tlvTag = GsmPDUHelper.readHexOctet(); |
|
12915 let tlvLen = GsmPDUHelper.readHexOctet(); |
|
12916 readLen += 2; // For tag and length fields. |
|
12917 switch (tlvTag) { |
|
12918 case SPDI_TAG_SPDI: |
|
12919 // The value part itself is a TLV. |
|
12920 continue; |
|
12921 case SPDI_TAG_PLMN_LIST: |
|
12922 // This PLMN list is what we want. |
|
12923 RIL.iccInfoPrivate.SPDI = this.readPLMNEntries(tlvLen / 3); |
|
12924 readLen += tlvLen; |
|
12925 endLoop = true; |
|
12926 break; |
|
12927 default: |
|
12928 // We don't care about its content if its tag is not SPDI nor |
|
12929 // PLMN_LIST. |
|
12930 endLoop = true; |
|
12931 break; |
|
12932 } |
|
12933 } |
|
12934 |
|
12935 // Consume unread octets. |
|
12936 Buf.seekIncoming((octetLen - readLen) * Buf.PDU_HEX_OCTET_SIZE); |
|
12937 Buf.readStringDelimiter(strLen); |
|
12938 |
|
12939 if (DEBUG) { |
|
12940 this.context.debug("SPDI: " + JSON.stringify(RIL.iccInfoPrivate.SPDI)); |
|
12941 } |
|
12942 let ICCUtilsHelper = this.context.ICCUtilsHelper; |
|
12943 if (ICCUtilsHelper.updateDisplayCondition()) { |
|
12944 ICCUtilsHelper.handleICCInfoChange(); |
|
12945 } |
|
12946 } |
|
12947 |
|
12948 // PLMN List is Servive 51 in USIM, EF_SPDI |
|
12949 this.context.ICCIOHelper.loadTransparentEF({ |
|
12950 fileId: ICC_EF_SPDI, |
|
12951 callback: callback.bind(this) |
|
12952 }); |
|
12953 }, |
|
12954 |
|
12955 _readCbmiHelper: function(which) { |
|
12956 let RIL = this.context.RIL; |
|
12957 |
|
12958 function callback() { |
|
12959 let Buf = this.context.Buf; |
|
12960 let strLength = Buf.readInt32(); |
|
12961 |
|
12962 // Each Message Identifier takes two octets and each octet is encoded |
|
12963 // into two chars. |
|
12964 let numIds = strLength / 4, list = null; |
|
12965 if (numIds) { |
|
12966 list = []; |
|
12967 let GsmPDUHelper = this.context.GsmPDUHelper; |
|
12968 for (let i = 0, id; i < numIds; i++) { |
|
12969 id = GsmPDUHelper.readHexOctet() << 8 | GsmPDUHelper.readHexOctet(); |
|
12970 // `Unused entries shall be set to 'FF FF'.` |
|
12971 if (id != 0xFFFF) { |
|
12972 list.push(id); |
|
12973 list.push(id + 1); |
|
12974 } |
|
12975 } |
|
12976 } |
|
12977 if (DEBUG) { |
|
12978 this.context.debug(which + ": " + JSON.stringify(list)); |
|
12979 } |
|
12980 |
|
12981 Buf.readStringDelimiter(strLength); |
|
12982 |
|
12983 RIL.cellBroadcastConfigs[which] = list; |
|
12984 RIL._mergeAllCellBroadcastConfigs(); |
|
12985 } |
|
12986 |
|
12987 function onerror() { |
|
12988 RIL.cellBroadcastConfigs[which] = null; |
|
12989 RIL._mergeAllCellBroadcastConfigs(); |
|
12990 } |
|
12991 |
|
12992 let fileId = GLOBAL["ICC_EF_" + which]; |
|
12993 this.context.ICCIOHelper.loadTransparentEF({ |
|
12994 fileId: fileId, |
|
12995 callback: callback.bind(this), |
|
12996 onerror: onerror.bind(this) |
|
12997 }); |
|
12998 }, |
|
12999 |
|
13000 /** |
|
13001 * Read EFcbmi (Cell Broadcast Message Identifier selection) |
|
13002 * |
|
13003 * @see 3GPP TS 31.102 v110.02.0 section 4.2.14 EFcbmi |
|
13004 * @see 3GPP TS 51.011 v5.0.0 section 10.3.13 EFcbmi |
|
13005 */ |
|
13006 readCBMI: function() { |
|
13007 this._readCbmiHelper("CBMI"); |
|
13008 }, |
|
13009 |
|
13010 /** |
|
13011 * Read EFcbmid (Cell Broadcast Message Identifier for Data Download) |
|
13012 * |
|
13013 * @see 3GPP TS 31.102 v110.02.0 section 4.2.20 EFcbmid |
|
13014 * @see 3GPP TS 51.011 v5.0.0 section 10.3.26 EFcbmid |
|
13015 */ |
|
13016 readCBMID: function() { |
|
13017 this._readCbmiHelper("CBMID"); |
|
13018 }, |
|
13019 |
|
13020 /** |
|
13021 * Read EFcbmir (Cell Broadcast Message Identifier Range selection) |
|
13022 * |
|
13023 * @see 3GPP TS 31.102 v110.02.0 section 4.2.22 EFcbmir |
|
13024 * @see 3GPP TS 51.011 v5.0.0 section 10.3.28 EFcbmir |
|
13025 */ |
|
13026 readCBMIR: function() { |
|
13027 let RIL = this.context.RIL; |
|
13028 |
|
13029 function callback() { |
|
13030 let Buf = this.context.Buf; |
|
13031 let strLength = Buf.readInt32(); |
|
13032 |
|
13033 // Each Message Identifier range takes four octets and each octet is |
|
13034 // encoded into two chars. |
|
13035 let numIds = strLength / 8, list = null; |
|
13036 if (numIds) { |
|
13037 list = []; |
|
13038 let GsmPDUHelper = this.context.GsmPDUHelper; |
|
13039 for (let i = 0, from, to; i < numIds; i++) { |
|
13040 // `Bytes one and two of each range identifier equal the lower value |
|
13041 // of a cell broadcast range, bytes three and four equal the upper |
|
13042 // value of a cell broadcast range.` |
|
13043 from = GsmPDUHelper.readHexOctet() << 8 | GsmPDUHelper.readHexOctet(); |
|
13044 to = GsmPDUHelper.readHexOctet() << 8 | GsmPDUHelper.readHexOctet(); |
|
13045 // `Unused entries shall be set to 'FF FF'.` |
|
13046 if ((from != 0xFFFF) && (to != 0xFFFF)) { |
|
13047 list.push(from); |
|
13048 list.push(to + 1); |
|
13049 } |
|
13050 } |
|
13051 } |
|
13052 if (DEBUG) { |
|
13053 this.context.debug("CBMIR: " + JSON.stringify(list)); |
|
13054 } |
|
13055 |
|
13056 Buf.readStringDelimiter(strLength); |
|
13057 |
|
13058 RIL.cellBroadcastConfigs.CBMIR = list; |
|
13059 RIL._mergeAllCellBroadcastConfigs(); |
|
13060 } |
|
13061 |
|
13062 function onerror() { |
|
13063 RIL.cellBroadcastConfigs.CBMIR = null; |
|
13064 RIL._mergeAllCellBroadcastConfigs(); |
|
13065 } |
|
13066 |
|
13067 this.context.ICCIOHelper.loadTransparentEF({ |
|
13068 fileId: ICC_EF_CBMIR, |
|
13069 callback: callback.bind(this), |
|
13070 onerror: onerror.bind(this) |
|
13071 }); |
|
13072 }, |
|
13073 |
|
13074 /** |
|
13075 * Read OPL (Operator PLMN List) from (U)SIM. |
|
13076 * |
|
13077 * See 3GPP TS 31.102 Sec. 4.2.59 for USIM |
|
13078 * 3GPP TS 51.011 Sec. 10.3.42 for SIM. |
|
13079 */ |
|
13080 readOPL: function() { |
|
13081 let ICCIOHelper = this.context.ICCIOHelper; |
|
13082 let opl = []; |
|
13083 function callback(options) { |
|
13084 let Buf = this.context.Buf; |
|
13085 let GsmPDUHelper = this.context.GsmPDUHelper; |
|
13086 |
|
13087 let strLen = Buf.readInt32(); |
|
13088 // The first 7 bytes are LAI (for UMTS) and the format of LAI is defined |
|
13089 // in 3GPP TS 23.003, Sec 4.1 |
|
13090 // +-------------+---------+ |
|
13091 // | Octet 1 - 3 | MCC/MNC | |
|
13092 // +-------------+---------+ |
|
13093 // | Octet 4 - 7 | LAC | |
|
13094 // +-------------+---------+ |
|
13095 let mccMnc = [GsmPDUHelper.readHexOctet(), |
|
13096 GsmPDUHelper.readHexOctet(), |
|
13097 GsmPDUHelper.readHexOctet()]; |
|
13098 if (mccMnc[0] != 0xFF || mccMnc[1] != 0xFF || mccMnc[2] != 0xFF) { |
|
13099 let oplElement = {}; |
|
13100 let semiOctets = []; |
|
13101 for (let i = 0; i < mccMnc.length; i++) { |
|
13102 semiOctets.push((mccMnc[i] & 0xf0) >> 4); |
|
13103 semiOctets.push(mccMnc[i] & 0x0f); |
|
13104 } |
|
13105 let reformat = [semiOctets[1], semiOctets[0], semiOctets[3], |
|
13106 semiOctets[5], semiOctets[4], semiOctets[2]]; |
|
13107 let buf = ""; |
|
13108 for (let i = 0; i < reformat.length; i++) { |
|
13109 if (reformat[i] != 0xF) { |
|
13110 buf += GsmPDUHelper.semiOctetToBcdChar(reformat[i]); |
|
13111 } |
|
13112 if (i === 2) { |
|
13113 // 0-2: MCC |
|
13114 oplElement.mcc = buf; |
|
13115 buf = ""; |
|
13116 } else if (i === 5) { |
|
13117 // 3-5: MNC |
|
13118 oplElement.mnc = buf; |
|
13119 } |
|
13120 } |
|
13121 // LAC/TAC |
|
13122 oplElement.lacTacStart = |
|
13123 (GsmPDUHelper.readHexOctet() << 8) | GsmPDUHelper.readHexOctet(); |
|
13124 oplElement.lacTacEnd = |
|
13125 (GsmPDUHelper.readHexOctet() << 8) | GsmPDUHelper.readHexOctet(); |
|
13126 // PLMN Network Name Record Identifier |
|
13127 oplElement.pnnRecordId = GsmPDUHelper.readHexOctet(); |
|
13128 if (DEBUG) { |
|
13129 this.context.debug("OPL: [" + (opl.length + 1) + "]: " + |
|
13130 JSON.stringify(oplElement)); |
|
13131 } |
|
13132 opl.push(oplElement); |
|
13133 } else { |
|
13134 Buf.seekIncoming(5 * Buf.PDU_HEX_OCTET_SIZE); |
|
13135 } |
|
13136 Buf.readStringDelimiter(strLen); |
|
13137 |
|
13138 if (options.p1 < options.totalRecords) { |
|
13139 ICCIOHelper.loadNextRecord(options); |
|
13140 } else { |
|
13141 this.context.RIL.iccInfoPrivate.OPL = opl; |
|
13142 } |
|
13143 } |
|
13144 |
|
13145 ICCIOHelper.loadLinearFixedEF({fileId: ICC_EF_OPL, |
|
13146 callback: callback.bind(this)}); |
|
13147 }, |
|
13148 |
|
13149 /** |
|
13150 * Read PNN (PLMN Network Name) from (U)SIM. |
|
13151 * |
|
13152 * See 3GPP TS 31.102 Sec. 4.2.58 for USIM |
|
13153 * 3GPP TS 51.011 Sec. 10.3.41 for SIM. |
|
13154 */ |
|
13155 readPNN: function() { |
|
13156 let ICCIOHelper = this.context.ICCIOHelper; |
|
13157 function callback(options) { |
|
13158 let pnnElement; |
|
13159 let Buf = this.context.Buf; |
|
13160 let strLen = Buf.readInt32(); |
|
13161 let octetLen = strLen / 2; |
|
13162 let readLen = 0; |
|
13163 |
|
13164 let GsmPDUHelper = this.context.GsmPDUHelper; |
|
13165 while (readLen < octetLen) { |
|
13166 let tlvTag = GsmPDUHelper.readHexOctet(); |
|
13167 |
|
13168 if (tlvTag == 0xFF) { |
|
13169 // Unused byte |
|
13170 readLen++; |
|
13171 Buf.seekIncoming((octetLen - readLen) * Buf.PDU_HEX_OCTET_SIZE); |
|
13172 break; |
|
13173 } |
|
13174 |
|
13175 // Needs this check to avoid initializing twice. |
|
13176 pnnElement = pnnElement || {}; |
|
13177 |
|
13178 let tlvLen = GsmPDUHelper.readHexOctet(); |
|
13179 |
|
13180 switch (tlvTag) { |
|
13181 case PNN_IEI_FULL_NETWORK_NAME: |
|
13182 pnnElement.fullName = GsmPDUHelper.readNetworkName(tlvLen); |
|
13183 break; |
|
13184 case PNN_IEI_SHORT_NETWORK_NAME: |
|
13185 pnnElement.shortName = GsmPDUHelper.readNetworkName(tlvLen); |
|
13186 break; |
|
13187 default: |
|
13188 Buf.seekIncoming(tlvLen * Buf.PDU_HEX_OCTET_SIZE); |
|
13189 break; |
|
13190 } |
|
13191 |
|
13192 readLen += (tlvLen + 2); // +2 for tlvTag and tlvLen |
|
13193 } |
|
13194 Buf.readStringDelimiter(strLen); |
|
13195 |
|
13196 if (pnnElement) { |
|
13197 pnn.push(pnnElement); |
|
13198 } |
|
13199 |
|
13200 // Will ignore remaining records when got the contents of a record are all 0xff. |
|
13201 if (pnnElement && options.p1 < options.totalRecords) { |
|
13202 ICCIOHelper.loadNextRecord(options); |
|
13203 } else { |
|
13204 if (DEBUG) { |
|
13205 for (let i = 0; i < pnn.length; i++) { |
|
13206 this.context.debug("PNN: [" + i + "]: " + JSON.stringify(pnn[i])); |
|
13207 } |
|
13208 } |
|
13209 this.context.RIL.iccInfoPrivate.PNN = pnn; |
|
13210 } |
|
13211 } |
|
13212 |
|
13213 let pnn = []; |
|
13214 ICCIOHelper.loadLinearFixedEF({fileId: ICC_EF_PNN, |
|
13215 callback: callback.bind(this)}); |
|
13216 }, |
|
13217 |
|
13218 /** |
|
13219 * Read the list of PLMN (Public Land Mobile Network) entries |
|
13220 * We cannot directly rely on readSwappedNibbleBcdToString(), |
|
13221 * since it will no correctly handle some corner-cases that are |
|
13222 * not a problem in our case (0xFF 0xFF 0xFF). |
|
13223 * |
|
13224 * @param length The number of PLMN records. |
|
13225 * @return An array of string corresponding to the PLMNs. |
|
13226 */ |
|
13227 readPLMNEntries: function(length) { |
|
13228 let plmnList = []; |
|
13229 // Each PLMN entry has 3 bytes. |
|
13230 if (DEBUG) { |
|
13231 this.context.debug("PLMN entries length = " + length); |
|
13232 } |
|
13233 let GsmPDUHelper = this.context.GsmPDUHelper; |
|
13234 let index = 0; |
|
13235 while (index < length) { |
|
13236 // Unused entries will be 0xFFFFFF, according to EF_SPDI |
|
13237 // specs (TS 131 102, section 4.2.66) |
|
13238 try { |
|
13239 let plmn = [GsmPDUHelper.readHexOctet(), |
|
13240 GsmPDUHelper.readHexOctet(), |
|
13241 GsmPDUHelper.readHexOctet()]; |
|
13242 if (DEBUG) { |
|
13243 this.context.debug("Reading PLMN entry: [" + index + "]: '" + plmn + "'"); |
|
13244 } |
|
13245 if (plmn[0] != 0xFF && |
|
13246 plmn[1] != 0xFF && |
|
13247 plmn[2] != 0xFF) { |
|
13248 let semiOctets = []; |
|
13249 for (let idx = 0; idx < plmn.length; idx++) { |
|
13250 semiOctets.push((plmn[idx] & 0xF0) >> 4); |
|
13251 semiOctets.push(plmn[idx] & 0x0F); |
|
13252 } |
|
13253 |
|
13254 // According to TS 24.301, 9.9.3.12, the semi octets is arranged |
|
13255 // in format: |
|
13256 // Byte 1: MCC[2] | MCC[1] |
|
13257 // Byte 2: MNC[3] | MCC[3] |
|
13258 // Byte 3: MNC[2] | MNC[1] |
|
13259 // Therefore, we need to rearrange them. |
|
13260 let reformat = [semiOctets[1], semiOctets[0], semiOctets[3], |
|
13261 semiOctets[5], semiOctets[4], semiOctets[2]]; |
|
13262 let buf = ""; |
|
13263 let plmnEntry = {}; |
|
13264 for (let i = 0; i < reformat.length; i++) { |
|
13265 if (reformat[i] != 0xF) { |
|
13266 buf += GsmPDUHelper.semiOctetToBcdChar(reformat[i]); |
|
13267 } |
|
13268 if (i === 2) { |
|
13269 // 0-2: MCC |
|
13270 plmnEntry.mcc = buf; |
|
13271 buf = ""; |
|
13272 } else if (i === 5) { |
|
13273 // 3-5: MNC |
|
13274 plmnEntry.mnc = buf; |
|
13275 } |
|
13276 } |
|
13277 if (DEBUG) { |
|
13278 this.context.debug("PLMN = " + plmnEntry.mcc + ", " + plmnEntry.mnc); |
|
13279 } |
|
13280 plmnList.push(plmnEntry); |
|
13281 } |
|
13282 } catch (e) { |
|
13283 if (DEBUG) { |
|
13284 this.context.debug("PLMN entry " + index + " is invalid."); |
|
13285 } |
|
13286 break; |
|
13287 } |
|
13288 index ++; |
|
13289 } |
|
13290 return plmnList; |
|
13291 }, |
|
13292 |
|
13293 /** |
|
13294 * Read the SMS from the ICC. |
|
13295 * |
|
13296 * @param recordNumber The number of the record shall be loaded. |
|
13297 * @param onsuccess Callback to be called when success. |
|
13298 * @param onerror Callback to be called when error. |
|
13299 */ |
|
13300 readSMS: function(recordNumber, onsuccess, onerror) { |
|
13301 function callback(options) { |
|
13302 let Buf = this.context.Buf; |
|
13303 let strLen = Buf.readInt32(); |
|
13304 |
|
13305 // TS 51.011, 10.5.3 EF_SMS |
|
13306 // b3 b2 b1 |
|
13307 // 0 0 1 message received by MS from network; message read |
|
13308 // 0 1 1 message received by MS from network; message to be read |
|
13309 // 1 1 1 MS originating message; message to be sent |
|
13310 // 1 0 1 MS originating message; message sent to the network: |
|
13311 let GsmPDUHelper = this.context.GsmPDUHelper; |
|
13312 let status = GsmPDUHelper.readHexOctet(); |
|
13313 |
|
13314 let message = GsmPDUHelper.readMessage(); |
|
13315 message.simStatus = status; |
|
13316 |
|
13317 // Consumes the remaining buffer |
|
13318 Buf.seekIncoming(Buf.getReadAvailable() - Buf.PDU_HEX_OCTET_SIZE); |
|
13319 |
|
13320 Buf.readStringDelimiter(strLen); |
|
13321 |
|
13322 if (message) { |
|
13323 onsuccess(message); |
|
13324 } else { |
|
13325 onerror("Failed to decode SMS on SIM #" + recordNumber); |
|
13326 } |
|
13327 } |
|
13328 |
|
13329 this.context.ICCIOHelper.loadLinearFixedEF({ |
|
13330 fileId: ICC_EF_SMS, |
|
13331 recordNumber: recordNumber, |
|
13332 callback: callback.bind(this), |
|
13333 onerror: onerror |
|
13334 }); |
|
13335 }, |
|
13336 }; |
|
13337 |
|
13338 function RuimRecordHelperObject(aContext) { |
|
13339 this.context = aContext; |
|
13340 } |
|
13341 RuimRecordHelperObject.prototype = { |
|
13342 context: null, |
|
13343 |
|
13344 fetchRuimRecords: function() { |
|
13345 this.getIMSI_M(); |
|
13346 this.readCST(); |
|
13347 this.readCDMAHome(); |
|
13348 this.context.RIL.getCdmaSubscription(); |
|
13349 }, |
|
13350 |
|
13351 /** |
|
13352 * Get IMSI_M from CSIM/RUIM. |
|
13353 * See 3GPP2 C.S0065 Sec. 5.2.2 |
|
13354 */ |
|
13355 getIMSI_M: function() { |
|
13356 function callback() { |
|
13357 let Buf = this.context.Buf; |
|
13358 let strLen = Buf.readInt32(); |
|
13359 let encodedImsi = this.context.GsmPDUHelper.readHexOctetArray(strLen / 2); |
|
13360 Buf.readStringDelimiter(strLen); |
|
13361 |
|
13362 if ((encodedImsi[CSIM_IMSI_M_PROGRAMMED_BYTE] & 0x80)) { // IMSI_M programmed |
|
13363 let RIL = this.context.RIL; |
|
13364 RIL.iccInfoPrivate.imsi = this.decodeIMSI(encodedImsi); |
|
13365 RIL.sendChromeMessage({rilMessageType: "iccimsi", |
|
13366 imsi: RIL.iccInfoPrivate.imsi}); |
|
13367 |
|
13368 let ICCUtilsHelper = this.context.ICCUtilsHelper; |
|
13369 let mccMnc = ICCUtilsHelper.parseMccMncFromImsi(RIL.iccInfoPrivate.imsi); |
|
13370 if (mccMnc) { |
|
13371 RIL.iccInfo.mcc = mccMnc.mcc; |
|
13372 RIL.iccInfo.mnc = mccMnc.mnc; |
|
13373 ICCUtilsHelper.handleICCInfoChange(); |
|
13374 } |
|
13375 } |
|
13376 } |
|
13377 |
|
13378 this.context.ICCIOHelper.loadTransparentEF({ |
|
13379 fileId: ICC_EF_CSIM_IMSI_M, |
|
13380 callback: callback.bind(this) |
|
13381 }); |
|
13382 }, |
|
13383 |
|
13384 /** |
|
13385 * Decode IMSI from IMSI_M |
|
13386 * See 3GPP2 C.S0005 Sec. 2.3.1 |
|
13387 * +---+---------+------------+---+--------+---------+---+---------+--------+ |
|
13388 * |RFU| MCC | programmed |RFU| MNC | MIN1 |RFU| MIN2 | CLASS | |
|
13389 * +---+---------+------------+---+--------+---------+---+---------+--------+ |
|
13390 * | 6 | 10 bits | 8 bits | 1 | 7 bits | 24 bits | 6 | 10 bits | 8 bits | |
|
13391 * +---+---------+------------+---+--------+---------+---+---------+--------+ |
|
13392 */ |
|
13393 decodeIMSI: function(encodedImsi) { |
|
13394 // MCC: 10 bits, 3 digits |
|
13395 let encodedMCC = ((encodedImsi[CSIM_IMSI_M_MCC_BYTE + 1] & 0x03) << 8) + |
|
13396 (encodedImsi[CSIM_IMSI_M_MCC_BYTE] & 0xff); |
|
13397 let mcc = this.decodeIMSIValue(encodedMCC, 3); |
|
13398 |
|
13399 // MNC: 7 bits, 2 digits |
|
13400 let encodedMNC = encodedImsi[CSIM_IMSI_M_MNC_BYTE] & 0x7f; |
|
13401 let mnc = this.decodeIMSIValue(encodedMNC, 2); |
|
13402 |
|
13403 // MIN2: 10 bits, 3 digits |
|
13404 let encodedMIN2 = ((encodedImsi[CSIM_IMSI_M_MIN2_BYTE + 1] & 0x03) << 8) + |
|
13405 (encodedImsi[CSIM_IMSI_M_MIN2_BYTE] & 0xff); |
|
13406 let min2 = this.decodeIMSIValue(encodedMIN2, 3); |
|
13407 |
|
13408 // MIN1: 10+4+10 bits, 3+1+3 digits |
|
13409 let encodedMIN1First3 = ((encodedImsi[CSIM_IMSI_M_MIN1_BYTE + 2] & 0xff) << 2) + |
|
13410 ((encodedImsi[CSIM_IMSI_M_MIN1_BYTE + 1] & 0xc0) >> 6); |
|
13411 let min1First3 = this.decodeIMSIValue(encodedMIN1First3, 3); |
|
13412 |
|
13413 let encodedFourthDigit = (encodedImsi[CSIM_IMSI_M_MIN1_BYTE + 1] & 0x3c) >> 2; |
|
13414 if (encodedFourthDigit > 9) { |
|
13415 encodedFourthDigit = 0; |
|
13416 } |
|
13417 let fourthDigit = encodedFourthDigit.toString(); |
|
13418 |
|
13419 let encodedMIN1Last3 = ((encodedImsi[CSIM_IMSI_M_MIN1_BYTE + 1] & 0x03) << 8) + |
|
13420 (encodedImsi[CSIM_IMSI_M_MIN1_BYTE] & 0xff); |
|
13421 let min1Last3 = this.decodeIMSIValue(encodedMIN1Last3, 3); |
|
13422 |
|
13423 return mcc + mnc + min2 + min1First3 + fourthDigit + min1Last3; |
|
13424 }, |
|
13425 |
|
13426 /** |
|
13427 * Decode IMSI Helper function |
|
13428 * See 3GPP2 C.S0005 section 2.3.1.1 |
|
13429 */ |
|
13430 decodeIMSIValue: function(encoded, length) { |
|
13431 let offset = length === 3 ? 111 : 11; |
|
13432 let value = encoded + offset; |
|
13433 |
|
13434 for (let base = 10, temp = value, i = 0; i < length; i++) { |
|
13435 if (temp % 10 === 0) { |
|
13436 value -= base; |
|
13437 } |
|
13438 temp = Math.floor(value / base); |
|
13439 base = base * 10; |
|
13440 } |
|
13441 |
|
13442 let s = value.toString(); |
|
13443 while (s.length < length) { |
|
13444 s = "0" + s; |
|
13445 } |
|
13446 |
|
13447 return s; |
|
13448 }, |
|
13449 |
|
13450 /** |
|
13451 * Read CDMAHOME for CSIM. |
|
13452 * See 3GPP2 C.S0023 Sec. 3.4.8. |
|
13453 */ |
|
13454 readCDMAHome: function() { |
|
13455 let ICCIOHelper = this.context.ICCIOHelper; |
|
13456 |
|
13457 function callback(options) { |
|
13458 let Buf = this.context.Buf; |
|
13459 let GsmPDUHelper = this.context.GsmPDUHelper; |
|
13460 |
|
13461 let strLen = Buf.readInt32(); |
|
13462 let tempOctet = GsmPDUHelper.readHexOctet(); |
|
13463 cdmaHomeSystemId.push(((GsmPDUHelper.readHexOctet() & 0x7f) << 8) | tempOctet); |
|
13464 tempOctet = GsmPDUHelper.readHexOctet(); |
|
13465 cdmaHomeNetworkId.push(((GsmPDUHelper.readHexOctet() & 0xff) << 8) | tempOctet); |
|
13466 |
|
13467 // Consuming the last octet: band class. |
|
13468 Buf.seekIncoming(Buf.PDU_HEX_OCTET_SIZE); |
|
13469 |
|
13470 Buf.readStringDelimiter(strLen); |
|
13471 if (options.p1 < options.totalRecords) { |
|
13472 ICCIOHelper.loadNextRecord(options); |
|
13473 } else { |
|
13474 if (DEBUG) { |
|
13475 this.context.debug("CDMAHome system id: " + |
|
13476 JSON.stringify(cdmaHomeSystemId)); |
|
13477 this.context.debug("CDMAHome network id: " + |
|
13478 JSON.stringify(cdmaHomeNetworkId)); |
|
13479 } |
|
13480 this.context.RIL.cdmaHome = { |
|
13481 systemId: cdmaHomeSystemId, |
|
13482 networkId: cdmaHomeNetworkId |
|
13483 }; |
|
13484 } |
|
13485 } |
|
13486 |
|
13487 let cdmaHomeSystemId = [], cdmaHomeNetworkId = []; |
|
13488 ICCIOHelper.loadLinearFixedEF({fileId: ICC_EF_CSIM_CDMAHOME, |
|
13489 callback: callback.bind(this)}); |
|
13490 }, |
|
13491 |
|
13492 /** |
|
13493 * Read CDMA Service Table. |
|
13494 * See 3GPP2 C.S0023 Sec. 3.4.18 |
|
13495 */ |
|
13496 readCST: function() { |
|
13497 function callback() { |
|
13498 let Buf = this.context.Buf; |
|
13499 let RIL = this.context.RIL; |
|
13500 |
|
13501 let strLen = Buf.readInt32(); |
|
13502 // Each octet is encoded into two chars. |
|
13503 RIL.iccInfoPrivate.cst = |
|
13504 this.context.GsmPDUHelper.readHexOctetArray(strLen / 2); |
|
13505 Buf.readStringDelimiter(strLen); |
|
13506 |
|
13507 if (DEBUG) { |
|
13508 let str = ""; |
|
13509 for (let i = 0; i < RIL.iccInfoPrivate.cst.length; i++) { |
|
13510 str += RIL.iccInfoPrivate.cst[i] + ", "; |
|
13511 } |
|
13512 this.context.debug("CST: " + str); |
|
13513 } |
|
13514 |
|
13515 if (this.context.ICCUtilsHelper.isICCServiceAvailable("SPN")) { |
|
13516 if (DEBUG) this.context.debug("SPN: SPN is available"); |
|
13517 this.readSPN(); |
|
13518 } |
|
13519 } |
|
13520 this.context.ICCIOHelper.loadTransparentEF({ |
|
13521 fileId: ICC_EF_CSIM_CST, |
|
13522 callback: callback.bind(this) |
|
13523 }); |
|
13524 }, |
|
13525 |
|
13526 readSPN: function() { |
|
13527 function callback() { |
|
13528 let Buf = this.context.Buf; |
|
13529 let strLen = Buf.readInt32(); |
|
13530 let octetLen = strLen / 2; |
|
13531 |
|
13532 let GsmPDUHelper = this.context.GsmPDUHelper; |
|
13533 let displayCondition = GsmPDUHelper.readHexOctet(); |
|
13534 let codingScheme = GsmPDUHelper.readHexOctet(); |
|
13535 // Skip one octet: language indicator. |
|
13536 Buf.seekIncoming(Buf.PDU_HEX_OCTET_SIZE); |
|
13537 let readLen = 3; |
|
13538 |
|
13539 // SPN String ends up with 0xff. |
|
13540 let userDataBuffer = []; |
|
13541 |
|
13542 while (readLen < octetLen) { |
|
13543 let octet = GsmPDUHelper.readHexOctet(); |
|
13544 readLen++; |
|
13545 if (octet == 0xff) { |
|
13546 break; |
|
13547 } |
|
13548 userDataBuffer.push(octet); |
|
13549 } |
|
13550 |
|
13551 this.context.BitBufferHelper.startRead(userDataBuffer); |
|
13552 |
|
13553 let CdmaPDUHelper = this.context.CdmaPDUHelper; |
|
13554 let msgLen; |
|
13555 switch (CdmaPDUHelper.getCdmaMsgEncoding(codingScheme)) { |
|
13556 case PDU_DCS_MSG_CODING_7BITS_ALPHABET: |
|
13557 msgLen = Math.floor(userDataBuffer.length * 8 / 7); |
|
13558 break; |
|
13559 case PDU_DCS_MSG_CODING_8BITS_ALPHABET: |
|
13560 msgLen = userDataBuffer.length; |
|
13561 break; |
|
13562 case PDU_DCS_MSG_CODING_16BITS_ALPHABET: |
|
13563 msgLen = Math.floor(userDataBuffer.length / 2); |
|
13564 break; |
|
13565 } |
|
13566 |
|
13567 let RIL = this.context.RIL; |
|
13568 RIL.iccInfo.spn = CdmaPDUHelper.decodeCdmaPDUMsg(codingScheme, null, msgLen); |
|
13569 if (DEBUG) { |
|
13570 this.context.debug("CDMA SPN: " + RIL.iccInfo.spn + |
|
13571 ", Display condition: " + displayCondition); |
|
13572 } |
|
13573 RIL.iccInfoPrivate.spnDisplayCondition = displayCondition; |
|
13574 Buf.seekIncoming((octetLen - readLen) * Buf.PDU_HEX_OCTET_SIZE); |
|
13575 Buf.readStringDelimiter(strLen); |
|
13576 } |
|
13577 |
|
13578 this.context.ICCIOHelper.loadTransparentEF({ |
|
13579 fileId: ICC_EF_CSIM_SPN, |
|
13580 callback: callback.bind(this) |
|
13581 }); |
|
13582 } |
|
13583 }; |
|
13584 |
|
13585 /** |
|
13586 * Helper functions for ICC utilities. |
|
13587 */ |
|
13588 function ICCUtilsHelperObject(aContext) { |
|
13589 this.context = aContext; |
|
13590 } |
|
13591 ICCUtilsHelperObject.prototype = { |
|
13592 context: null, |
|
13593 |
|
13594 /** |
|
13595 * Get network names by using EF_OPL and EF_PNN |
|
13596 * |
|
13597 * @See 3GPP TS 31.102 sec. 4.2.58 and sec. 4.2.59 for USIM, |
|
13598 * 3GPP TS 51.011 sec. 10.3.41 and sec. 10.3.42 for SIM. |
|
13599 * |
|
13600 * @param mcc The mobile country code of the network. |
|
13601 * @param mnc The mobile network code of the network. |
|
13602 * @param lac The location area code of the network. |
|
13603 */ |
|
13604 getNetworkNameFromICC: function(mcc, mnc, lac) { |
|
13605 let RIL = this.context.RIL; |
|
13606 let iccInfoPriv = RIL.iccInfoPrivate; |
|
13607 let iccInfo = RIL.iccInfo; |
|
13608 let pnnEntry; |
|
13609 |
|
13610 if (!mcc || !mnc || !lac) { |
|
13611 return null; |
|
13612 } |
|
13613 |
|
13614 // We won't get network name if there is no PNN file. |
|
13615 if (!iccInfoPriv.PNN) { |
|
13616 return null; |
|
13617 } |
|
13618 |
|
13619 if (!iccInfoPriv.OPL) { |
|
13620 // When OPL is not present: |
|
13621 // According to 3GPP TS 31.102 Sec. 4.2.58 and 3GPP TS 51.011 Sec. 10.3.41, |
|
13622 // If EF_OPL is not present, the first record in this EF is used for the |
|
13623 // default network name when registered to the HPLMN. |
|
13624 // If we haven't get pnnEntry assigned, we should try to assign default |
|
13625 // value to it. |
|
13626 if (mcc == iccInfo.mcc && mnc == iccInfo.mnc) { |
|
13627 pnnEntry = iccInfoPriv.PNN[0]; |
|
13628 } |
|
13629 } else { |
|
13630 // According to 3GPP TS 31.102 Sec. 4.2.59 and 3GPP TS 51.011 Sec. 10.3.42, |
|
13631 // the ME shall use this EF_OPL in association with the EF_PNN in place |
|
13632 // of any network name stored within the ME's internal list and any network |
|
13633 // name received when registered to the PLMN. |
|
13634 let length = iccInfoPriv.OPL ? iccInfoPriv.OPL.length : 0; |
|
13635 for (let i = 0; i < length; i++) { |
|
13636 let opl = iccInfoPriv.OPL[i]; |
|
13637 // Try to match the MCC/MNC. |
|
13638 if (mcc != opl.mcc || mnc != opl.mnc) { |
|
13639 continue; |
|
13640 } |
|
13641 // Try to match the location area code. If current local area code is |
|
13642 // covered by lac range that specified in the OPL entry, use the PNN |
|
13643 // that specified in the OPL entry. |
|
13644 if ((opl.lacTacStart === 0x0 && opl.lacTacEnd == 0xFFFE) || |
|
13645 (opl.lacTacStart <= lac && opl.lacTacEnd >= lac)) { |
|
13646 if (opl.pnnRecordId === 0) { |
|
13647 // See 3GPP TS 31.102 Sec. 4.2.59 and 3GPP TS 51.011 Sec. 10.3.42, |
|
13648 // A value of '00' indicates that the name is to be taken from other |
|
13649 // sources. |
|
13650 return null; |
|
13651 } |
|
13652 pnnEntry = iccInfoPriv.PNN[opl.pnnRecordId - 1]; |
|
13653 break; |
|
13654 } |
|
13655 } |
|
13656 } |
|
13657 |
|
13658 if (!pnnEntry) { |
|
13659 return null; |
|
13660 } |
|
13661 |
|
13662 // Return a new object to avoid global variable, PNN, be modified by accident. |
|
13663 return { fullName: pnnEntry.fullName || "", |
|
13664 shortName: pnnEntry.shortName || "" }; |
|
13665 }, |
|
13666 |
|
13667 /** |
|
13668 * This will compute the spnDisplay field of the network. |
|
13669 * See TS 22.101 Annex A and TS 51.011 10.3.11 for details. |
|
13670 * |
|
13671 * @return True if some of iccInfo is changed in by this function. |
|
13672 */ |
|
13673 updateDisplayCondition: function() { |
|
13674 let RIL = this.context.RIL; |
|
13675 |
|
13676 // If EFspn isn't existed in SIM or it haven't been read yet, we should |
|
13677 // just set isDisplayNetworkNameRequired = true and |
|
13678 // isDisplaySpnRequired = false |
|
13679 let iccInfo = RIL.iccInfo; |
|
13680 let iccInfoPriv = RIL.iccInfoPrivate; |
|
13681 let displayCondition = iccInfoPriv.spnDisplayCondition; |
|
13682 let origIsDisplayNetworkNameRequired = iccInfo.isDisplayNetworkNameRequired; |
|
13683 let origIsDisplaySPNRequired = iccInfo.isDisplaySpnRequired; |
|
13684 |
|
13685 if (displayCondition === undefined) { |
|
13686 iccInfo.isDisplayNetworkNameRequired = true; |
|
13687 iccInfo.isDisplaySpnRequired = false; |
|
13688 } else if (RIL._isCdma) { |
|
13689 // CDMA family display rule. |
|
13690 let cdmaHome = RIL.cdmaHome; |
|
13691 let cell = RIL.voiceRegistrationState.cell; |
|
13692 let sid = cell && cell.cdmaSystemId; |
|
13693 let nid = cell && cell.cdmaNetworkId; |
|
13694 |
|
13695 iccInfo.isDisplayNetworkNameRequired = false; |
|
13696 |
|
13697 // If display condition is 0x0, we don't even need to check network id |
|
13698 // or system id. |
|
13699 if (displayCondition === 0x0) { |
|
13700 iccInfo.isDisplaySpnRequired = false; |
|
13701 } else { |
|
13702 // CDMA SPN Display condition dosen't specify whenever network name is |
|
13703 // reqired. |
|
13704 if (!cdmaHome || |
|
13705 !cdmaHome.systemId || |
|
13706 cdmaHome.systemId.length === 0 || |
|
13707 cdmaHome.systemId.length != cdmaHome.networkId.length || |
|
13708 !sid || !nid) { |
|
13709 // CDMA Home haven't been ready, or we haven't got the system id and |
|
13710 // network id of the network we register to, assuming we are in home |
|
13711 // network. |
|
13712 iccInfo.isDisplaySpnRequired = true; |
|
13713 } else { |
|
13714 // Determine if we are registered in the home service area. |
|
13715 // System ID and Network ID are described in 3GPP2 C.S0005 Sec. 2.6.5.2. |
|
13716 let inHomeArea = false; |
|
13717 for (let i = 0; i < cdmaHome.systemId.length; i++) { |
|
13718 let homeSid = cdmaHome.systemId[i], |
|
13719 homeNid = cdmaHome.networkId[i]; |
|
13720 if (homeSid === 0 || homeNid === 0 // Reserved system id/network id |
|
13721 || homeSid != sid) { |
|
13722 continue; |
|
13723 } |
|
13724 // According to 3GPP2 C.S0005 Sec. 2.6.5.2, NID number 65535 means |
|
13725 // all networks in the system should be considered as home. |
|
13726 if (homeNid == 65535 || homeNid == nid) { |
|
13727 inHomeArea = true; |
|
13728 break; |
|
13729 } |
|
13730 } |
|
13731 iccInfo.isDisplaySpnRequired = inHomeArea; |
|
13732 } |
|
13733 } |
|
13734 } else { |
|
13735 // GSM family display rule. |
|
13736 let operatorMnc = RIL.operator.mnc; |
|
13737 let operatorMcc = RIL.operator.mcc; |
|
13738 |
|
13739 // First detect if we are on HPLMN or one of the PLMN |
|
13740 // specified by the SIM card. |
|
13741 let isOnMatchingPlmn = false; |
|
13742 |
|
13743 // If the current network is the one defined as mcc/mnc |
|
13744 // in SIM card, it's okay. |
|
13745 if (iccInfo.mcc == operatorMcc && iccInfo.mnc == operatorMnc) { |
|
13746 isOnMatchingPlmn = true; |
|
13747 } |
|
13748 |
|
13749 // Test to see if operator's mcc/mnc match mcc/mnc of PLMN. |
|
13750 if (!isOnMatchingPlmn && iccInfoPriv.SPDI) { |
|
13751 let iccSpdi = iccInfoPriv.SPDI; // PLMN list |
|
13752 for (let plmn in iccSpdi) { |
|
13753 let plmnMcc = iccSpdi[plmn].mcc; |
|
13754 let plmnMnc = iccSpdi[plmn].mnc; |
|
13755 isOnMatchingPlmn = (plmnMcc == operatorMcc) && (plmnMnc == operatorMnc); |
|
13756 if (isOnMatchingPlmn) { |
|
13757 break; |
|
13758 } |
|
13759 } |
|
13760 } |
|
13761 |
|
13762 if (isOnMatchingPlmn) { |
|
13763 // The first bit of display condition tells us if we should display |
|
13764 // registered PLMN. |
|
13765 if (DEBUG) { |
|
13766 this.context.debug("PLMN is HPLMN or PLMN " + "is in PLMN list"); |
|
13767 } |
|
13768 |
|
13769 // TS 31.102 Sec. 4.2.66 and TS 51.011 Sec. 10.3.50 |
|
13770 // EF_SPDI contains a list of PLMNs in which the Service Provider Name |
|
13771 // shall be displayed. |
|
13772 iccInfo.isDisplaySpnRequired = true; |
|
13773 iccInfo.isDisplayNetworkNameRequired = (displayCondition & 0x01) !== 0; |
|
13774 } else { |
|
13775 // The second bit of display condition tells us if we should display |
|
13776 // registered PLMN. |
|
13777 if (DEBUG) { |
|
13778 this.context.debug("PLMN isn't HPLMN and PLMN isn't in PLMN list"); |
|
13779 } |
|
13780 |
|
13781 // We didn't found the requirement of displaying network name if |
|
13782 // current PLMN isn't HPLMN nor one of PLMN in SPDI. So we keep |
|
13783 // isDisplayNetworkNameRequired false. |
|
13784 iccInfo.isDisplayNetworkNameRequired = false; |
|
13785 iccInfo.isDisplaySpnRequired = (displayCondition & 0x02) === 0; |
|
13786 } |
|
13787 } |
|
13788 |
|
13789 if (DEBUG) { |
|
13790 this.context.debug("isDisplayNetworkNameRequired = " + |
|
13791 iccInfo.isDisplayNetworkNameRequired); |
|
13792 this.context.debug("isDisplaySpnRequired = " + iccInfo.isDisplaySpnRequired); |
|
13793 } |
|
13794 |
|
13795 return ((origIsDisplayNetworkNameRequired !== iccInfo.isDisplayNetworkNameRequired) || |
|
13796 (origIsDisplaySPNRequired !== iccInfo.isDisplaySpnRequired)); |
|
13797 }, |
|
13798 |
|
13799 decodeSimTlvs: function(tlvsLen) { |
|
13800 let GsmPDUHelper = this.context.GsmPDUHelper; |
|
13801 |
|
13802 let index = 0; |
|
13803 let tlvs = []; |
|
13804 while (index < tlvsLen) { |
|
13805 let simTlv = { |
|
13806 tag : GsmPDUHelper.readHexOctet(), |
|
13807 length : GsmPDUHelper.readHexOctet(), |
|
13808 }; |
|
13809 simTlv.value = GsmPDUHelper.readHexOctetArray(simTlv.length); |
|
13810 tlvs.push(simTlv); |
|
13811 index += simTlv.length + 2; // The length of 'tag' and 'length' field. |
|
13812 } |
|
13813 return tlvs; |
|
13814 }, |
|
13815 |
|
13816 /** |
|
13817 * Parse those TLVs and convert it to an object. |
|
13818 */ |
|
13819 parsePbrTlvs: function(pbrTlvs) { |
|
13820 let pbr = {}; |
|
13821 for (let i = 0; i < pbrTlvs.length; i++) { |
|
13822 let pbrTlv = pbrTlvs[i]; |
|
13823 let anrIndex = 0; |
|
13824 for (let j = 0; j < pbrTlv.value.length; j++) { |
|
13825 let tlv = pbrTlv.value[j]; |
|
13826 let tagName = USIM_TAG_NAME[tlv.tag]; |
|
13827 |
|
13828 // ANR could have multiple files. We save it as anr0, anr1,...etc. |
|
13829 if (tlv.tag == ICC_USIM_EFANR_TAG) { |
|
13830 tagName += anrIndex; |
|
13831 anrIndex++; |
|
13832 } |
|
13833 pbr[tagName] = tlv; |
|
13834 pbr[tagName].fileType = pbrTlv.tag; |
|
13835 pbr[tagName].fileId = (tlv.value[0] << 8) | tlv.value[1]; |
|
13836 pbr[tagName].sfi = tlv.value[2]; |
|
13837 |
|
13838 // For Type 2, the order of files is in the same order in IAP. |
|
13839 if (pbrTlv.tag == ICC_USIM_TYPE2_TAG) { |
|
13840 pbr[tagName].indexInIAP = j; |
|
13841 } |
|
13842 } |
|
13843 } |
|
13844 |
|
13845 return pbr; |
|
13846 }, |
|
13847 |
|
13848 /** |
|
13849 * Update the ICC information to RadioInterfaceLayer. |
|
13850 */ |
|
13851 handleICCInfoChange: function() { |
|
13852 let RIL = this.context.RIL; |
|
13853 RIL.iccInfo.rilMessageType = "iccinfochange"; |
|
13854 RIL.sendChromeMessage(RIL.iccInfo); |
|
13855 }, |
|
13856 |
|
13857 /** |
|
13858 * Get whether specificed (U)SIM service is available. |
|
13859 * |
|
13860 * @param geckoService |
|
13861 * Service name like "ADN", "BDN", etc. |
|
13862 * |
|
13863 * @return true if the service is enabled, false otherwise. |
|
13864 */ |
|
13865 isICCServiceAvailable: function(geckoService) { |
|
13866 let RIL = this.context.RIL; |
|
13867 let serviceTable = RIL._isCdma ? RIL.iccInfoPrivate.cst: |
|
13868 RIL.iccInfoPrivate.sst; |
|
13869 let index, bitmask; |
|
13870 if (RIL.appType == CARD_APPTYPE_SIM || RIL.appType == CARD_APPTYPE_RUIM) { |
|
13871 /** |
|
13872 * Service id is valid in 1..N, and 2 bits are used to code each service. |
|
13873 * |
|
13874 * +----+-- --+----+----+ |
|
13875 * | b8 | ... | b2 | b1 | |
|
13876 * +----+-- --+----+----+ |
|
13877 * |
|
13878 * b1 = 0, service not allocated. |
|
13879 * 1, service allocated. |
|
13880 * b2 = 0, service not activatd. |
|
13881 * 1, service allocated. |
|
13882 * |
|
13883 * @see 3GPP TS 51.011 10.3.7. |
|
13884 */ |
|
13885 let simService; |
|
13886 if (RIL.appType == CARD_APPTYPE_SIM) { |
|
13887 simService = GECKO_ICC_SERVICES.sim[geckoService]; |
|
13888 } else { |
|
13889 simService = GECKO_ICC_SERVICES.ruim[geckoService]; |
|
13890 } |
|
13891 if (!simService) { |
|
13892 return false; |
|
13893 } |
|
13894 simService -= 1; |
|
13895 index = Math.floor(simService / 4); |
|
13896 bitmask = 2 << ((simService % 4) << 1); |
|
13897 } else if (RIL.appType == CARD_APPTYPE_USIM) { |
|
13898 /** |
|
13899 * Service id is valid in 1..N, and 1 bit is used to code each service. |
|
13900 * |
|
13901 * +----+-- --+----+----+ |
|
13902 * | b8 | ... | b2 | b1 | |
|
13903 * +----+-- --+----+----+ |
|
13904 * |
|
13905 * b1 = 0, service not avaiable. |
|
13906 * 1, service available. |
|
13907 * b2 = 0, service not avaiable. |
|
13908 * 1, service available. |
|
13909 * |
|
13910 * @see 3GPP TS 31.102 4.2.8. |
|
13911 */ |
|
13912 let usimService = GECKO_ICC_SERVICES.usim[geckoService]; |
|
13913 if (!usimService) { |
|
13914 return false; |
|
13915 } |
|
13916 usimService -= 1; |
|
13917 index = Math.floor(usimService / 8); |
|
13918 bitmask = 1 << ((usimService % 8) << 0); |
|
13919 } |
|
13920 |
|
13921 return (serviceTable !== null) && |
|
13922 (index < serviceTable.length) && |
|
13923 ((serviceTable[index] & bitmask) !== 0); |
|
13924 }, |
|
13925 |
|
13926 /** |
|
13927 * Check if the string is of GSM default 7-bit coded alphabets with bit 8 |
|
13928 * set to 0. |
|
13929 * |
|
13930 * @param str String to be checked. |
|
13931 */ |
|
13932 isGsm8BitAlphabet: function(str) { |
|
13933 if (!str) { |
|
13934 return false; |
|
13935 } |
|
13936 |
|
13937 const langTable = PDU_NL_LOCKING_SHIFT_TABLES[PDU_NL_IDENTIFIER_DEFAULT]; |
|
13938 const langShiftTable = PDU_NL_SINGLE_SHIFT_TABLES[PDU_NL_IDENTIFIER_DEFAULT]; |
|
13939 |
|
13940 for (let i = 0; i < str.length; i++) { |
|
13941 let c = str.charAt(i); |
|
13942 let octet = langTable.indexOf(c); |
|
13943 if (octet == -1) { |
|
13944 octet = langShiftTable.indexOf(c); |
|
13945 if (octet == -1) { |
|
13946 return false; |
|
13947 } |
|
13948 } |
|
13949 } |
|
13950 |
|
13951 return true; |
|
13952 }, |
|
13953 |
|
13954 /** |
|
13955 * Parse MCC/MNC from IMSI. If there is no available value for the length of |
|
13956 * mnc, it will use the data in MCC table to parse. |
|
13957 * |
|
13958 * @param imsi |
|
13959 * The imsi of icc. |
|
13960 * @param mncLength [optional] |
|
13961 * The length of mnc. |
|
13962 * |
|
13963 * @return An object contains the parsing result of mcc and mnc. |
|
13964 * Or null if any error occurred. |
|
13965 */ |
|
13966 parseMccMncFromImsi: function(imsi, mncLength) { |
|
13967 if (!imsi) { |
|
13968 return null; |
|
13969 } |
|
13970 |
|
13971 // MCC is the first 3 digits of IMSI. |
|
13972 let mcc = imsi.substr(0,3); |
|
13973 if (!mncLength) { |
|
13974 // Check the MCC table to decide the length of MNC. |
|
13975 let index = MCC_TABLE_FOR_MNC_LENGTH_IS_3.indexOf(mcc); |
|
13976 mncLength = (index !== -1) ? 3 : 2; |
|
13977 } |
|
13978 let mnc = imsi.substr(3, mncLength); |
|
13979 if (DEBUG) { |
|
13980 this.context.debug("IMSI: " + imsi + " MCC: " + mcc + " MNC: " + mnc); |
|
13981 } |
|
13982 |
|
13983 return { mcc: mcc, mnc: mnc}; |
|
13984 }, |
|
13985 }; |
|
13986 |
|
13987 /** |
|
13988 * Helper for ICC Contacts. |
|
13989 */ |
|
13990 function ICCContactHelperObject(aContext) { |
|
13991 this.context = aContext; |
|
13992 } |
|
13993 ICCContactHelperObject.prototype = { |
|
13994 context: null, |
|
13995 |
|
13996 /** |
|
13997 * Helper function to check DF_PHONEBOOK. |
|
13998 */ |
|
13999 hasDfPhoneBook: function(appType) { |
|
14000 switch (appType) { |
|
14001 case CARD_APPTYPE_SIM: |
|
14002 return false; |
|
14003 case CARD_APPTYPE_USIM: |
|
14004 return true; |
|
14005 case CARD_APPTYPE_RUIM: |
|
14006 let ICCUtilsHelper = this.context.ICCUtilsHelper; |
|
14007 return ICCUtilsHelper.isICCServiceAvailable("ENHANCED_PHONEBOOK"); |
|
14008 default: |
|
14009 return false; |
|
14010 } |
|
14011 }, |
|
14012 |
|
14013 /** |
|
14014 * Helper function to read ICC contacts. |
|
14015 * |
|
14016 * @param appType One of CARD_APPTYPE_*. |
|
14017 * @param contactType "adn" or "fdn". |
|
14018 * @param onsuccess Callback to be called when success. |
|
14019 * @param onerror Callback to be called when error. |
|
14020 */ |
|
14021 readICCContacts: function(appType, contactType, onsuccess, onerror) { |
|
14022 let ICCRecordHelper = this.context.ICCRecordHelper; |
|
14023 |
|
14024 switch (contactType) { |
|
14025 case "adn": |
|
14026 if (!this.hasDfPhoneBook(appType)) { |
|
14027 ICCRecordHelper.readADNLike(ICC_EF_ADN, onsuccess, onerror); |
|
14028 } else { |
|
14029 this.readUSimContacts(onsuccess, onerror); |
|
14030 } |
|
14031 break; |
|
14032 case "fdn": |
|
14033 ICCRecordHelper.readADNLike(ICC_EF_FDN, onsuccess, onerror); |
|
14034 break; |
|
14035 default: |
|
14036 if (DEBUG) { |
|
14037 this.context.debug("Unsupported contactType :" + contactType); |
|
14038 } |
|
14039 onerror(CONTACT_ERR_CONTACT_TYPE_NOT_SUPPORTED); |
|
14040 break; |
|
14041 } |
|
14042 }, |
|
14043 |
|
14044 /** |
|
14045 * Helper function to find free contact record. |
|
14046 * |
|
14047 * @param appType One of CARD_APPTYPE_*. |
|
14048 * @param contactType "adn" or "fdn". |
|
14049 * @param onsuccess Callback to be called when success. |
|
14050 * @param onerror Callback to be called when error. |
|
14051 */ |
|
14052 findFreeICCContact: function(appType, contactType, onsuccess, onerror) { |
|
14053 let ICCRecordHelper = this.context.ICCRecordHelper; |
|
14054 |
|
14055 switch (contactType) { |
|
14056 case "adn": |
|
14057 if (!this.hasDfPhoneBook(appType)) { |
|
14058 ICCRecordHelper.findFreeRecordId(ICC_EF_ADN, onsuccess.bind(null, 0), onerror); |
|
14059 } else { |
|
14060 let gotPbrCb = function gotPbrCb(pbrs) { |
|
14061 this.findUSimFreeADNRecordId(pbrs, onsuccess, onerror); |
|
14062 }.bind(this); |
|
14063 |
|
14064 ICCRecordHelper.readPBR(gotPbrCb, onerror); |
|
14065 } |
|
14066 break; |
|
14067 case "fdn": |
|
14068 ICCRecordHelper.findFreeRecordId(ICC_EF_FDN, onsuccess.bind(null, 0), onerror); |
|
14069 break; |
|
14070 default: |
|
14071 if (DEBUG) { |
|
14072 this.context.debug("Unsupported contactType :" + contactType); |
|
14073 } |
|
14074 onerror(CONTACT_ERR_CONTACT_TYPE_NOT_SUPPORTED); |
|
14075 break; |
|
14076 } |
|
14077 }, |
|
14078 |
|
14079 /** |
|
14080 * Find free ADN record id in USIM. |
|
14081 * |
|
14082 * @param pbrs All Phonebook Reference Files read. |
|
14083 * @param onsuccess Callback to be called when success. |
|
14084 * @param onerror Callback to be called when error. |
|
14085 */ |
|
14086 findUSimFreeADNRecordId: function(pbrs, onsuccess, onerror) { |
|
14087 let ICCRecordHelper = this.context.ICCRecordHelper; |
|
14088 |
|
14089 (function findFreeRecordId(pbrIndex) { |
|
14090 if (pbrIndex >= pbrs.length) { |
|
14091 if (DEBUG) { |
|
14092 this.context.debug(CONTACT_ERR_NO_FREE_RECORD_FOUND); |
|
14093 } |
|
14094 onerror(CONTACT_ERR_NO_FREE_RECORD_FOUND); |
|
14095 return; |
|
14096 } |
|
14097 |
|
14098 let pbr = pbrs[pbrIndex]; |
|
14099 ICCRecordHelper.findFreeRecordId( |
|
14100 pbr.adn.fileId, |
|
14101 onsuccess.bind(this, pbrIndex), |
|
14102 findFreeRecordId.bind(null, pbrIndex + 1)); |
|
14103 })(0); |
|
14104 }, |
|
14105 |
|
14106 /** |
|
14107 * Helper function to add a new ICC contact. |
|
14108 * |
|
14109 * @param appType One of CARD_APPTYPE_*. |
|
14110 * @param contactType "adn" or "fdn". |
|
14111 * @param contact The contact will be added. |
|
14112 * @param pin2 PIN2 is required for FDN. |
|
14113 * @param onsuccess Callback to be called when success. |
|
14114 * @param onerror Callback to be called when error. |
|
14115 */ |
|
14116 addICCContact: function(appType, contactType, contact, pin2, onsuccess, onerror) { |
|
14117 let foundFreeCb = (function foundFreeCb(pbrIndex, recordId) { |
|
14118 contact.pbrIndex = pbrIndex; |
|
14119 contact.recordId = recordId; |
|
14120 this.updateICCContact(appType, contactType, contact, pin2, onsuccess, onerror); |
|
14121 }).bind(this); |
|
14122 |
|
14123 // Find free record first. |
|
14124 this.findFreeICCContact(appType, contactType, foundFreeCb, onerror); |
|
14125 }, |
|
14126 |
|
14127 /** |
|
14128 * Helper function to update ICC contact. |
|
14129 * |
|
14130 * @param appType One of CARD_APPTYPE_*. |
|
14131 * @param contactType "adn" or "fdn". |
|
14132 * @param contact The contact will be updated. |
|
14133 * @param pin2 PIN2 is required for FDN. |
|
14134 * @param onsuccess Callback to be called when success. |
|
14135 * @param onerror Callback to be called when error. |
|
14136 */ |
|
14137 updateICCContact: function(appType, contactType, contact, pin2, onsuccess, onerror) { |
|
14138 let ICCRecordHelper = this.context.ICCRecordHelper; |
|
14139 |
|
14140 switch (contactType) { |
|
14141 case "adn": |
|
14142 if (!this.hasDfPhoneBook(appType)) { |
|
14143 ICCRecordHelper.updateADNLike(ICC_EF_ADN, contact, null, onsuccess, onerror); |
|
14144 } else { |
|
14145 this.updateUSimContact(contact, onsuccess, onerror); |
|
14146 } |
|
14147 break; |
|
14148 case "fdn": |
|
14149 if (!pin2) { |
|
14150 onerror(GECKO_ERROR_SIM_PIN2); |
|
14151 return; |
|
14152 } |
|
14153 ICCRecordHelper.updateADNLike(ICC_EF_FDN, contact, pin2, onsuccess, onerror); |
|
14154 break; |
|
14155 default: |
|
14156 if (DEBUG) { |
|
14157 this.context.debug("Unsupported contactType :" + contactType); |
|
14158 } |
|
14159 onerror(CONTACT_ERR_CONTACT_TYPE_NOT_SUPPORTED); |
|
14160 break; |
|
14161 } |
|
14162 }, |
|
14163 |
|
14164 /** |
|
14165 * Read contacts from USIM. |
|
14166 * |
|
14167 * @param onsuccess Callback to be called when success. |
|
14168 * @param onerror Callback to be called when error. |
|
14169 */ |
|
14170 readUSimContacts: function(onsuccess, onerror) { |
|
14171 let gotPbrCb = function gotPbrCb(pbrs) { |
|
14172 this.readAllPhonebookSets(pbrs, onsuccess, onerror); |
|
14173 }.bind(this); |
|
14174 |
|
14175 this.context.ICCRecordHelper.readPBR(gotPbrCb, onerror); |
|
14176 }, |
|
14177 |
|
14178 /** |
|
14179 * Read all Phonebook sets. |
|
14180 * |
|
14181 * @param pbrs All Phonebook Reference Files read. |
|
14182 * @param onsuccess Callback to be called when success. |
|
14183 * @param onerror Callback to be called when error. |
|
14184 */ |
|
14185 readAllPhonebookSets: function(pbrs, onsuccess, onerror) { |
|
14186 let allContacts = [], pbrIndex = 0; |
|
14187 let readPhonebook = function readPhonebook(contacts) { |
|
14188 if (contacts) { |
|
14189 allContacts = allContacts.concat(contacts); |
|
14190 } |
|
14191 |
|
14192 let cLen = contacts ? contacts.length : 0; |
|
14193 for (let i = 0; i < cLen; i++) { |
|
14194 contacts[i].pbrIndex = pbrIndex; |
|
14195 } |
|
14196 |
|
14197 pbrIndex++; |
|
14198 if (pbrIndex >= pbrs.length) { |
|
14199 if (onsuccess) { |
|
14200 onsuccess(allContacts); |
|
14201 } |
|
14202 return; |
|
14203 } |
|
14204 |
|
14205 this.readPhonebookSet(pbrs[pbrIndex], readPhonebook, onerror); |
|
14206 }.bind(this); |
|
14207 |
|
14208 this.readPhonebookSet(pbrs[pbrIndex], readPhonebook, onerror); |
|
14209 }, |
|
14210 |
|
14211 /** |
|
14212 * Read from Phonebook Reference File. |
|
14213 * |
|
14214 * @param pbr Phonebook Reference File to be read. |
|
14215 * @param onsuccess Callback to be called when success. |
|
14216 * @param onerror Callback to be called when error. |
|
14217 */ |
|
14218 readPhonebookSet: function(pbr, onsuccess, onerror) { |
|
14219 let gotAdnCb = function gotAdnCb(contacts) { |
|
14220 this.readSupportedPBRFields(pbr, contacts, onsuccess, onerror); |
|
14221 }.bind(this); |
|
14222 |
|
14223 this.context.ICCRecordHelper.readADNLike(pbr.adn.fileId, gotAdnCb, onerror); |
|
14224 }, |
|
14225 |
|
14226 /** |
|
14227 * Read supported Phonebook fields. |
|
14228 * |
|
14229 * @param pbr Phone Book Reference file. |
|
14230 * @param contacts Contacts stored on ICC. |
|
14231 * @param onsuccess Callback to be called when success. |
|
14232 * @param onerror Callback to be called when error. |
|
14233 */ |
|
14234 readSupportedPBRFields: function(pbr, contacts, onsuccess, onerror) { |
|
14235 let fieldIndex = 0; |
|
14236 (function readField() { |
|
14237 let field = USIM_PBR_FIELDS[fieldIndex]; |
|
14238 fieldIndex += 1; |
|
14239 if (!field) { |
|
14240 if (onsuccess) { |
|
14241 onsuccess(contacts); |
|
14242 } |
|
14243 return; |
|
14244 } |
|
14245 |
|
14246 this.readPhonebookField(pbr, contacts, field, readField.bind(this), onerror); |
|
14247 }).call(this); |
|
14248 }, |
|
14249 |
|
14250 /** |
|
14251 * Read Phonebook field. |
|
14252 * |
|
14253 * @param pbr The phonebook reference file. |
|
14254 * @param contacts Contacts stored on ICC. |
|
14255 * @param field Phonebook field to be retrieved. |
|
14256 * @param onsuccess Callback to be called when success. |
|
14257 * @param onerror Callback to be called when error. |
|
14258 */ |
|
14259 readPhonebookField: function(pbr, contacts, field, onsuccess, onerror) { |
|
14260 if (!pbr[field]) { |
|
14261 if (onsuccess) { |
|
14262 onsuccess(contacts); |
|
14263 } |
|
14264 return; |
|
14265 } |
|
14266 |
|
14267 (function doReadContactField(n) { |
|
14268 if (n >= contacts.length) { |
|
14269 // All contact's fields are read. |
|
14270 if (onsuccess) { |
|
14271 onsuccess(contacts); |
|
14272 } |
|
14273 return; |
|
14274 } |
|
14275 |
|
14276 // get n-th contact's field. |
|
14277 this.readContactField(pbr, contacts[n], field, |
|
14278 doReadContactField.bind(this, n + 1), onerror); |
|
14279 }).call(this, 0); |
|
14280 }, |
|
14281 |
|
14282 /** |
|
14283 * Read contact's field from USIM. |
|
14284 * |
|
14285 * @param pbr The phonebook reference file. |
|
14286 * @param contact The contact needs to get field. |
|
14287 * @param field Phonebook field to be retrieved. |
|
14288 * @param onsuccess Callback to be called when success. |
|
14289 * @param onerror Callback to be called when error. |
|
14290 */ |
|
14291 readContactField: function(pbr, contact, field, onsuccess, onerror) { |
|
14292 let gotRecordIdCb = function gotRecordIdCb(recordId) { |
|
14293 if (recordId == 0xff) { |
|
14294 if (onsuccess) { |
|
14295 onsuccess(); |
|
14296 } |
|
14297 return; |
|
14298 } |
|
14299 |
|
14300 let fileId = pbr[field].fileId; |
|
14301 let fileType = pbr[field].fileType; |
|
14302 let gotFieldCb = function gotFieldCb(value) { |
|
14303 if (value) { |
|
14304 // Move anr0 anr1,.. into anr[]. |
|
14305 if (field.startsWith(USIM_PBR_ANR)) { |
|
14306 if (!contact[USIM_PBR_ANR]) { |
|
14307 contact[USIM_PBR_ANR] = []; |
|
14308 } |
|
14309 contact[USIM_PBR_ANR].push(value); |
|
14310 } else { |
|
14311 contact[field] = value; |
|
14312 } |
|
14313 } |
|
14314 |
|
14315 if (onsuccess) { |
|
14316 onsuccess(); |
|
14317 } |
|
14318 }.bind(this); |
|
14319 |
|
14320 let ICCRecordHelper = this.context.ICCRecordHelper; |
|
14321 // Detect EF to be read, for anr, it could have anr0, anr1,... |
|
14322 let ef = field.startsWith(USIM_PBR_ANR) ? USIM_PBR_ANR : field; |
|
14323 switch (ef) { |
|
14324 case USIM_PBR_EMAIL: |
|
14325 ICCRecordHelper.readEmail(fileId, fileType, recordId, gotFieldCb, onerror); |
|
14326 break; |
|
14327 case USIM_PBR_ANR: |
|
14328 ICCRecordHelper.readANR(fileId, fileType, recordId, gotFieldCb, onerror); |
|
14329 break; |
|
14330 default: |
|
14331 if (DEBUG) { |
|
14332 this.context.debug("Unsupported field :" + field); |
|
14333 } |
|
14334 onerror(CONTACT_ERR_FIELD_NOT_SUPPORTED); |
|
14335 break; |
|
14336 } |
|
14337 }.bind(this); |
|
14338 |
|
14339 this.getContactFieldRecordId(pbr, contact, field, gotRecordIdCb, onerror); |
|
14340 }, |
|
14341 |
|
14342 /** |
|
14343 * Get the recordId. |
|
14344 * |
|
14345 * If the fileType of field is ICC_USIM_TYPE1_TAG, use corresponding ADN recordId. |
|
14346 * otherwise get the recordId from IAP. |
|
14347 * |
|
14348 * @see TS 131.102, clause 4.4.2.2 |
|
14349 * |
|
14350 * @param pbr The phonebook reference file. |
|
14351 * @param contact The contact will be updated. |
|
14352 * @param onsuccess Callback to be called when success. |
|
14353 * @param onerror Callback to be called when error. |
|
14354 */ |
|
14355 getContactFieldRecordId: function(pbr, contact, field, onsuccess, onerror) { |
|
14356 if (pbr[field].fileType == ICC_USIM_TYPE1_TAG) { |
|
14357 // If the file type is ICC_USIM_TYPE1_TAG, use corresponding ADN recordId. |
|
14358 if (onsuccess) { |
|
14359 onsuccess(contact.recordId); |
|
14360 } |
|
14361 } else if (pbr[field].fileType == ICC_USIM_TYPE2_TAG) { |
|
14362 // If the file type is ICC_USIM_TYPE2_TAG, the recordId shall be got from IAP. |
|
14363 let gotIapCb = function gotIapCb(iap) { |
|
14364 let indexInIAP = pbr[field].indexInIAP; |
|
14365 let recordId = iap[indexInIAP]; |
|
14366 |
|
14367 if (onsuccess) { |
|
14368 onsuccess(recordId); |
|
14369 } |
|
14370 }.bind(this); |
|
14371 |
|
14372 this.context.ICCRecordHelper.readIAP(pbr.iap.fileId, contact.recordId, |
|
14373 gotIapCb, onerror); |
|
14374 } else { |
|
14375 if (DEBUG) { |
|
14376 this.context.debug("USIM PBR files in Type 3 format are not supported."); |
|
14377 } |
|
14378 onerror(CONTACT_ERR_REQUEST_NOT_SUPPORTED); |
|
14379 } |
|
14380 }, |
|
14381 |
|
14382 /** |
|
14383 * Update USIM contact. |
|
14384 * |
|
14385 * @param contact The contact will be updated. |
|
14386 * @param onsuccess Callback to be called when success. |
|
14387 * @param onerror Callback to be called when error. |
|
14388 */ |
|
14389 updateUSimContact: function(contact, onsuccess, onerror) { |
|
14390 let gotPbrCb = function gotPbrCb(pbrs) { |
|
14391 let pbr = pbrs[contact.pbrIndex]; |
|
14392 if (!pbr) { |
|
14393 if (DEBUG) { |
|
14394 this.context.debug(CONTACT_ERR_CANNOT_ACCESS_PHONEBOOK); |
|
14395 } |
|
14396 onerror(CONTACT_ERR_CANNOT_ACCESS_PHONEBOOK); |
|
14397 return; |
|
14398 } |
|
14399 this.updatePhonebookSet(pbr, contact, onsuccess, onerror); |
|
14400 }.bind(this); |
|
14401 |
|
14402 this.context.ICCRecordHelper.readPBR(gotPbrCb, onerror); |
|
14403 }, |
|
14404 |
|
14405 /** |
|
14406 * Update fields in Phonebook Reference File. |
|
14407 * |
|
14408 * @param pbr Phonebook Reference File to be read. |
|
14409 * @param onsuccess Callback to be called when success. |
|
14410 * @param onerror Callback to be called when error. |
|
14411 */ |
|
14412 updatePhonebookSet: function(pbr, contact, onsuccess, onerror) { |
|
14413 let updateAdnCb = function() { |
|
14414 this.updateSupportedPBRFields(pbr, contact, onsuccess, onerror); |
|
14415 }.bind(this); |
|
14416 |
|
14417 this.context.ICCRecordHelper.updateADNLike(pbr.adn.fileId, contact, null, |
|
14418 updateAdnCb, onerror); |
|
14419 }, |
|
14420 |
|
14421 /** |
|
14422 * Update supported Phonebook fields. |
|
14423 * |
|
14424 * @param pbr Phone Book Reference file. |
|
14425 * @param contact Contact to be updated. |
|
14426 * @param onsuccess Callback to be called when success. |
|
14427 * @param onerror Callback to be called when error. |
|
14428 */ |
|
14429 updateSupportedPBRFields: function(pbr, contact, onsuccess, onerror) { |
|
14430 let fieldIndex = 0; |
|
14431 (function updateField() { |
|
14432 let field = USIM_PBR_FIELDS[fieldIndex]; |
|
14433 fieldIndex += 1; |
|
14434 if (!field) { |
|
14435 if (onsuccess) { |
|
14436 onsuccess(); |
|
14437 } |
|
14438 return; |
|
14439 } |
|
14440 |
|
14441 // Check if PBR has this field. |
|
14442 if (!pbr[field]) { |
|
14443 updateField.call(this); |
|
14444 return; |
|
14445 } |
|
14446 |
|
14447 this.updateContactField(pbr, contact, field, updateField.bind(this), onerror); |
|
14448 }).call(this); |
|
14449 }, |
|
14450 |
|
14451 /** |
|
14452 * Update contact's field from USIM. |
|
14453 * |
|
14454 * @param pbr The phonebook reference file. |
|
14455 * @param contact The contact needs to be updated. |
|
14456 * @param field Phonebook field to be updated. |
|
14457 * @param onsuccess Callback to be called when success. |
|
14458 * @param onerror Callback to be called when error. |
|
14459 */ |
|
14460 updateContactField: function(pbr, contact, field, onsuccess, onerror) { |
|
14461 if (pbr[field].fileType === ICC_USIM_TYPE1_TAG) { |
|
14462 this.updateContactFieldType1(pbr, contact, field, onsuccess, onerror); |
|
14463 } else if (pbr[field].fileType === ICC_USIM_TYPE2_TAG) { |
|
14464 this.updateContactFieldType2(pbr, contact, field, onsuccess, onerror); |
|
14465 } else { |
|
14466 if (DEBUG) { |
|
14467 this.context.debug("USIM PBR files in Type 3 format are not supported."); |
|
14468 } |
|
14469 onerror(CONTACT_ERR_REQUEST_NOT_SUPPORTED); |
|
14470 } |
|
14471 }, |
|
14472 |
|
14473 /** |
|
14474 * Update Type 1 USIM contact fields. |
|
14475 * |
|
14476 * @param pbr The phonebook reference file. |
|
14477 * @param contact The contact needs to be updated. |
|
14478 * @param field Phonebook field to be updated. |
|
14479 * @param onsuccess Callback to be called when success. |
|
14480 * @param onerror Callback to be called when error. |
|
14481 */ |
|
14482 updateContactFieldType1: function(pbr, contact, field, onsuccess, onerror) { |
|
14483 let ICCRecordHelper = this.context.ICCRecordHelper; |
|
14484 |
|
14485 if (field === USIM_PBR_EMAIL) { |
|
14486 ICCRecordHelper.updateEmail(pbr, contact.recordId, contact.email, null, onsuccess, onerror); |
|
14487 } else if (field === USIM_PBR_ANR0) { |
|
14488 let anr = Array.isArray(contact.anr) ? contact.anr[0] : null; |
|
14489 ICCRecordHelper.updateANR(pbr, contact.recordId, anr, null, onsuccess, onerror); |
|
14490 } else { |
|
14491 if (DEBUG) { |
|
14492 this.context.debug("Unsupported field :" + field); |
|
14493 } |
|
14494 onerror(CONTACT_ERR_FIELD_NOT_SUPPORTED); |
|
14495 } |
|
14496 }, |
|
14497 |
|
14498 /** |
|
14499 * Update Type 2 USIM contact fields. |
|
14500 * |
|
14501 * @param pbr The phonebook reference file. |
|
14502 * @param contact The contact needs to be updated. |
|
14503 * @param field Phonebook field to be updated. |
|
14504 * @param onsuccess Callback to be called when success. |
|
14505 * @param onerror Callback to be called when error. |
|
14506 */ |
|
14507 updateContactFieldType2: function(pbr, contact, field, onsuccess, onerror) { |
|
14508 let ICCRecordHelper = this.context.ICCRecordHelper; |
|
14509 |
|
14510 // Case 1 : EF_IAP[adnRecordId] doesn't have a value(0xff) |
|
14511 // Find a free recordId for EF_field |
|
14512 // Update field with that free recordId. |
|
14513 // Update IAP. |
|
14514 // |
|
14515 // Case 2: EF_IAP[adnRecordId] has a value |
|
14516 // update EF_field[iap[field.indexInIAP]] |
|
14517 |
|
14518 let gotIapCb = function gotIapCb(iap) { |
|
14519 let recordId = iap[pbr[field].indexInIAP]; |
|
14520 if (recordId === 0xff) { |
|
14521 // If the value in IAP[index] is 0xff, which means the contact stored on |
|
14522 // the SIM doesn't have the additional attribute (email or anr). |
|
14523 // So if the contact to be updated doesn't have the attribute either, |
|
14524 // we don't have to update it. |
|
14525 if ((field === USIM_PBR_EMAIL && contact.email) || |
|
14526 (field === USIM_PBR_ANR0 && |
|
14527 (Array.isArray(contact.anr) && contact.anr[0]))) { |
|
14528 // Case 1. |
|
14529 this.addContactFieldType2(pbr, contact, field, onsuccess, onerror); |
|
14530 } else { |
|
14531 if (onsuccess) { |
|
14532 onsuccess(); |
|
14533 } |
|
14534 } |
|
14535 return; |
|
14536 } |
|
14537 |
|
14538 // Case 2. |
|
14539 if (field === USIM_PBR_EMAIL) { |
|
14540 ICCRecordHelper.updateEmail(pbr, recordId, contact.email, contact.recordId, onsuccess, onerror); |
|
14541 } else if (field === USIM_PBR_ANR0) { |
|
14542 let anr = Array.isArray(contact.anr) ? contact.anr[0] : null; |
|
14543 ICCRecordHelper.updateANR(pbr, recordId, anr, contact.recordId, onsuccess, onerror); |
|
14544 } else { |
|
14545 if (DEBUG) { |
|
14546 this.context.debug("Unsupported field :" + field); |
|
14547 } |
|
14548 onerror(CONTACT_ERR_FIELD_NOT_SUPPORTED); |
|
14549 } |
|
14550 |
|
14551 }.bind(this); |
|
14552 |
|
14553 ICCRecordHelper.readIAP(pbr.iap.fileId, contact.recordId, gotIapCb, onerror); |
|
14554 }, |
|
14555 |
|
14556 /** |
|
14557 * Add Type 2 USIM contact fields. |
|
14558 * |
|
14559 * @param pbr The phonebook reference file. |
|
14560 * @param contact The contact needs to be updated. |
|
14561 * @param field Phonebook field to be updated. |
|
14562 * @param onsuccess Callback to be called when success. |
|
14563 * @param onerror Callback to be called when error. |
|
14564 */ |
|
14565 addContactFieldType2: function(pbr, contact, field, onsuccess, onerror) { |
|
14566 let ICCRecordHelper = this.context.ICCRecordHelper; |
|
14567 |
|
14568 let successCb = function successCb(recordId) { |
|
14569 let updateCb = function updateCb() { |
|
14570 this.updateContactFieldIndexInIAP(pbr, contact.recordId, field, recordId, onsuccess, onerror); |
|
14571 }.bind(this); |
|
14572 |
|
14573 if (field === USIM_PBR_EMAIL) { |
|
14574 ICCRecordHelper.updateEmail(pbr, recordId, contact.email, contact.recordId, updateCb, onerror); |
|
14575 } else if (field === USIM_PBR_ANR0) { |
|
14576 ICCRecordHelper.updateANR(pbr, recordId, contact.anr[0], contact.recordId, updateCb, onerror); |
|
14577 } |
|
14578 }.bind(this); |
|
14579 |
|
14580 let errorCb = function errorCb(errorMsg) { |
|
14581 if (DEBUG) { |
|
14582 this.context.debug(errorMsg + " USIM field " + field); |
|
14583 } |
|
14584 onerror(errorMsg); |
|
14585 }.bind(this); |
|
14586 |
|
14587 ICCRecordHelper.findFreeRecordId(pbr[field].fileId, successCb, errorCb); |
|
14588 }, |
|
14589 |
|
14590 /** |
|
14591 * Update IAP value. |
|
14592 * |
|
14593 * @param pbr The phonebook reference file. |
|
14594 * @param recordNumber The record identifier of EF_IAP. |
|
14595 * @param field Phonebook field. |
|
14596 * @param value The value of 'field' in IAP. |
|
14597 * @param onsuccess Callback to be called when success. |
|
14598 * @param onerror Callback to be called when error. |
|
14599 * |
|
14600 */ |
|
14601 updateContactFieldIndexInIAP: function(pbr, recordNumber, field, value, onsuccess, onerror) { |
|
14602 let ICCRecordHelper = this.context.ICCRecordHelper; |
|
14603 |
|
14604 let gotIAPCb = function gotIAPCb(iap) { |
|
14605 iap[pbr[field].indexInIAP] = value; |
|
14606 ICCRecordHelper.updateIAP(pbr.iap.fileId, recordNumber, iap, onsuccess, onerror); |
|
14607 }.bind(this); |
|
14608 ICCRecordHelper.readIAP(pbr.iap.fileId, recordNumber, gotIAPCb, onerror); |
|
14609 }, |
|
14610 }; |
|
14611 |
|
14612 /** |
|
14613 * Global stuff. |
|
14614 */ |
|
14615 |
|
14616 function Context(aClientId) { |
|
14617 this.clientId = aClientId; |
|
14618 |
|
14619 this.Buf = new BufObject(this); |
|
14620 this.Buf.init(); |
|
14621 |
|
14622 this.RIL = new RilObject(this); |
|
14623 this.RIL.initRILState(); |
|
14624 } |
|
14625 Context.prototype = { |
|
14626 clientId: null, |
|
14627 Buf: null, |
|
14628 RIL: null, |
|
14629 |
|
14630 debug: function(aMessage) { |
|
14631 GLOBAL.debug("[" + this.clientId + "] " + aMessage); |
|
14632 } |
|
14633 }; |
|
14634 |
|
14635 (function() { |
|
14636 let lazySymbols = [ |
|
14637 "BerTlvHelper", "BitBufferHelper", "CdmaPDUHelper", |
|
14638 "ComprehensionTlvHelper", "GsmPDUHelper", "ICCContactHelper", |
|
14639 "ICCFileHelper", "ICCIOHelper", "ICCPDUHelper", "ICCRecordHelper", |
|
14640 "ICCUtilsHelper", "RuimRecordHelper", "SimRecordHelper", |
|
14641 "StkCommandParamsFactory", "StkProactiveCmdHelper", |
|
14642 ]; |
|
14643 |
|
14644 for (let i = 0; i < lazySymbols.length; i++) { |
|
14645 let symbol = lazySymbols[i]; |
|
14646 Object.defineProperty(Context.prototype, symbol, { |
|
14647 get: function() { |
|
14648 let real = new GLOBAL[symbol + "Object"](this); |
|
14649 Object.defineProperty(this, symbol, { |
|
14650 value: real, |
|
14651 enumerable: true |
|
14652 }); |
|
14653 return real; |
|
14654 }, |
|
14655 configurable: true, |
|
14656 enumerable: true |
|
14657 }); |
|
14658 } |
|
14659 })(); |
|
14660 |
|
14661 let ContextPool = { |
|
14662 _contexts: [], |
|
14663 |
|
14664 handleRilMessage: function(aClientId, aUint8Array) { |
|
14665 let context = this._contexts[aClientId]; |
|
14666 context.Buf.processIncoming(aUint8Array); |
|
14667 }, |
|
14668 |
|
14669 handleChromeMessage: function(aMessage) { |
|
14670 let clientId = aMessage.rilMessageClientId; |
|
14671 if (clientId != null) { |
|
14672 let context = this._contexts[clientId]; |
|
14673 context.RIL.handleChromeMessage(aMessage); |
|
14674 return; |
|
14675 } |
|
14676 |
|
14677 if (DEBUG) debug("Received global chrome message " + JSON.stringify(aMessage)); |
|
14678 let method = this[aMessage.rilMessageType]; |
|
14679 if (typeof method != "function") { |
|
14680 if (DEBUG) { |
|
14681 debug("Don't know what to do"); |
|
14682 } |
|
14683 return; |
|
14684 } |
|
14685 method.call(this, aMessage); |
|
14686 }, |
|
14687 |
|
14688 setInitialOptions: function(aOptions) { |
|
14689 DEBUG = DEBUG_WORKER || aOptions.debug; |
|
14690 RIL_EMERGENCY_NUMBERS = aOptions.rilEmergencyNumbers; |
|
14691 RIL_CELLBROADCAST_DISABLED = aOptions.cellBroadcastDisabled; |
|
14692 RIL_CLIR_MODE = aOptions.clirMode; |
|
14693 |
|
14694 let quirks = aOptions.quirks; |
|
14695 RILQUIRKS_CALLSTATE_EXTRA_UINT32 = quirks.callstateExtraUint32; |
|
14696 RILQUIRKS_V5_LEGACY = quirks.v5Legacy; |
|
14697 RILQUIRKS_REQUEST_USE_DIAL_EMERGENCY_CALL = quirks.requestUseDialEmergencyCall; |
|
14698 RILQUIRKS_SIM_APP_STATE_EXTRA_FIELDS = quirks.simAppStateExtraFields; |
|
14699 RILQUIRKS_EXTRA_UINT32_2ND_CALL = quirks.extraUint2ndCall; |
|
14700 RILQUIRKS_HAVE_QUERY_ICC_LOCK_RETRY_COUNT = quirks.haveQueryIccLockRetryCount; |
|
14701 RILQUIRKS_SEND_STK_PROFILE_DOWNLOAD = quirks.sendStkProfileDownload; |
|
14702 RILQUIRKS_DATA_REGISTRATION_ON_DEMAND = quirks.dataRegistrationOnDemand; |
|
14703 }, |
|
14704 |
|
14705 registerClient: function(aOptions) { |
|
14706 let clientId = aOptions.clientId; |
|
14707 this._contexts[clientId] = new Context(clientId); |
|
14708 }, |
|
14709 }; |
|
14710 |
|
14711 function onRILMessage(aClientId, aUint8Array) { |
|
14712 ContextPool.handleRilMessage(aClientId, aUint8Array); |
|
14713 } |
|
14714 |
|
14715 onmessage = function onmessage(event) { |
|
14716 ContextPool.handleChromeMessage(event.data); |
|
14717 }; |
|
14718 |
|
14719 onerror = function onerror(event) { |
|
14720 if (DEBUG) debug("onerror" + event.message + "\n"); |
|
14721 }; |